神马笔记实现共享方程公式
神马笔记实现共享方程公式
在前面的2个开发阶段,分别实现了『神马笔记』在文章模式和大纲模式下编辑方程公式元素。
- 文章模式——《Android完美实现在笔记中插入方程公式》
- 大纲模式——《神马笔记的方程公式大纲模式》
这次开发的目标是实现共享方程公式,以实现知识分享。
一、目标
完成共享方程公式,以实现知识分享的目标。
二、共享方式
序号 | 方式 | 说明 |
---|---|---|
1 | 拷贝到粘贴板 | 仅复制文字内容,包括段落、图片描述、方程描述、…… |
2 | 保存为图片 | 保存笔记截图,并添加笔名 |
3 | 保存为纯文本 | 与"拷贝到粘贴板"相同,将内容保存到文本文件 |
4 | 保存为Markdown | 以markdown格式输出笔记元素 |
5 | 保存为Hexo Markdown | 在Markdown的基础上增加hexo front-matter, 并按照hexo资源方式保存图片。 |
6 | 发送到第三方应用 | 同时发送笔记截图与文字内容,有第三方应用自行选择内容。 |
三、实现过程
1. 拷贝到粘贴板
依次将各种类型的DocumentEntity
对象转化为ClipData.Item
对象。
List<ClipData.Item> list = document.getList().stream()
.map(e -> {
ClipData.Item item;
Class clz = e.getClass();
if (clz == ParagraphEntity.class) {
ParagraphEntity entity = (ParagraphEntity)e;
item = new ClipData.Item(entity.getText());
} else if (clz == PictureEntity.class) {
PictureEntity entity = (PictureEntity)e;
item = new ClipData.Item(entity.getText());
} else if (clz == FormulaEntity.class) {
FormulaEntity entity = (FormulaEntity)e;
StringBuilder sb = new StringBuilder();
// formula
if (!TextUtils.isEmpty(entity.getFormula())) {
sb.append(entity.getFormula());
} else {
sb.append(entity.getLatex());
}
// description
if (!TextUtils.isEmpty(entity.getText())) {
sb.append('\n');
sb.append(entity.getText());
}
item = new ClipData.Item(sb);
} else {
item = new ClipData.Item("");
}
return item;
})
.collect(Collectors.toList());
if/else
的结构似乎应该优化一下了,采用类方式会更理想些。
2. 保存为图片
将View
绘制到离屏Bitmap
并保存之。
public Bitmap apply() {
Mode mode = chooseMode(view);
if (mode == null) {
return null;
}
Bitmap target = Bitmap.createBitmap(mode.mWidth, mode.mHeight, mode.mConfig);
Canvas canvas = new Canvas(target);
if (mode.mWidth != mode.mSourceWidth) {
float scale = 1.f * mode.mWidth / mode.mSourceWidth;
canvas.scale(scale, scale);
}
view.draw(canvas);
return target;
}
3. 保存为纯文本
与拷贝到粘贴板类似,将各种类型的DocumentEntity
对象添加到StringBuilder
对象,然后保存之。
StringBuilder sb = new StringBuilder(10 * 1024);
document.getList().stream()
.forEach(e -> {
Class clz = e.getClass();
if (clz == ParagraphEntity.class) {
ParagraphEntity entity = (ParagraphEntity)e;
sb.append(entity.getText());
} else if (clz == PictureEntity.class) {
PictureEntity entity = (PictureEntity)e;
sb.append(entity.getText());
} else if (clz == FormulaEntity.class) {
FormulaEntity entity = (FormulaEntity)e;
// formula
if (!TextUtils.isEmpty(entity.getFormula())) {
sb.append(entity.getFormula());
} else {
sb.append(entity.getLatex());
}
// description
if (!TextUtils.isEmpty(entity.getText())) {
sb.append('\n');
sb.append(entity.getText());
}
}
sb.append('\n');
});
4. 保存为Markdown
与保存到纯文本类似,将各种类型的DocumentEntity
转换为CharSequence
对象,并保存之。
Map<Class<? extends DocumentEntity>, Function<? super DocumentEntity, CharSequence>> createFactory(final boolean preview) {
HashMap<Class<? extends DocumentEntity>, Function<? super DocumentEntity, CharSequence>> map = new HashMap<>();
map.put(ParagraphEntity.class, e -> {
ParagraphEntity entity = (ParagraphEntity)e;
CharSequence text = entity.getText();
return text;
});
map.put(PictureEntity.class, e -> {
PictureEntity entity = (PictureEntity)e;
String name = pictureMap.get(entity);
StringBuilder sb = new StringBuilder();
sb.append(String.format("![%1$s]", entity.getText()));
sb.append(String.format("(%1$s)", name));
return sb;
});
map.put(FormulaEntity.class, e -> {
FormulaEntity entity = (FormulaEntity)e;
StringBuilder sb = new StringBuilder();
if (preview) {
sb.append("<p>");
}
String formula = entity.getFormula();
formula = (TextUtils.isEmpty(formula))? entity.getLatex(): formula;
if (MathMLTransformer.isMathML(formula)) {
sb.append(formula);
} else {
sb.append("$$\n");
sb.append(getLatex(formula, preview));
sb.append("\n$$");
}
if (preview) {
sb.append("</p>");
}
return sb;
});
return map;
}
5. 保存为Hexo Markdown
在markdown的基础上,添加hexo front-matter信息。
并按照hexo assets资源方式存储图片。
String createFrontMatter(RecordEntity entity) {
StringBuilder sb = new StringBuilder(1024);
sb.append("---\n");
// uuid
{
sb.append("uuid: ");
sb.append(getUUID(entity));
sb.append('\n');
}
// title
{
sb.append("title: ");
sb.append(entity.getName());
sb.append('\n');
}
// categories
{
String value = getCategories(entity);
if (!TextUtils.isEmpty(value)) {
sb.append("categories: ");
sb.append(value);
sb.append('\n');
}
}
// tags
{
String value = getTags(entity);
if (!TextUtils.isEmpty(value)) {
sb.append("tags: ");
sb.append(value);
sb.append('\n');
}
}
// mathjax
if (hasFormula(document)) {
sb.append("mathjax: true\n");
}
// date
{
sb.append("date: ");
sb.append(getDate(entity));
sb.append('\n');
}
sb.append("---\n\n");
String text = sb.toString();
return text;
}
6. 发送到第三方应用
包含文本和截图2个内容。
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
boolean hasImage = (uri != null);
String text = getText(document, getFooter()).toString();
// mime-type
{
intent.setType("image/*");
intent.setAction(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if (!hasImage) {
intent.setType("text/plain");
} else {
if (hasImage) {
hasImage = isSupport(context, resolveInfo, intent);
if (!hasImage) {
intent.setType("text/plain");
}
}
}
}
// component
{
String pkg = resolveInfo.activityInfo.packageName;
String cls = resolveInfo.activityInfo.name;
ComponentName cn = new ComponentName(pkg, cls);
intent.setComponent(cn);
}
// text
if (!TextUtils.isEmpty(text)) {
intent.putExtra(Intent.EXTRA_TEXT, text);
}
// picture
if (hasImage) {
if (intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND)) {
intent.removeExtra(Intent.EXTRA_STREAM);
intent.putExtra(Intent.EXTRA_STREAM, uri);
}
if (intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND_MULTIPLE)) {
ArrayList<Uri> imageUris = new ArrayList();
imageUris.add(uri); // Add your image URIs here
intent.removeExtra(Intent.EXTRA_STREAM);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
}
}
// start
try {
context.startActivity(intent);
} catch (Exception e) {
}
四、预览Markdown
1. 使用HTML预览
在6中分享方式中,2种Markdown方式无法直接根据输入内容进行预览。
Android平台并没有内置支持Markdown的应用,无法直接预览。
因此需要转换为HTML格式以实现预览。
序号 | 方式 | 预览 |
---|---|---|
1 | 拷贝到粘贴板 | 与正文一致 |
2 | 保存为图片 | 与正文一致 |
3 | 保存为纯文本 | 与正文一致 |
4 | 保存为Markdown | 使用HTML进行预览 |
5 | 保存为Hexo Markdown | 使用HTML进行预览 |
6 | 发送到第三方应用 | 与正文一致 |
2. 遇到的问题
在添加方程公式元素之前,一切工作良好。
添加了方程公式元素之后,会发现很多方程公式无法正常预览。
原因在于marked
与mathjax
发生了冲突。
模块 | 说明 |
---|---|
marked | 将markdown文档渲染为html文档 |
mathjax | 渲染html文档中的方程公式,支持latex及mathml |
在marked渲染markdown文档时,对一些特殊字符进行了转义,同是将latex中的一些字符进行了转义。从而导致latex公式遭到破坏,进而无法正常显示公式。
详细冲突细节,请参考一下文章。
- 在Hexo中渲染MathJax数学公式
- 【Markdown】Markdown 使用MathJax引擎 书写Latex 数学公式
- Hexo下mathjax的转义问题
- 让marked与MathJax和谐共存
3. 解决方案
文章中给出了2种解决冲突的方案。
方案 | 问题 |
---|---|
修改marked | 使用在线marked,无法修改 |
修改markdown渲染引擎为pandoc | 需要另外安装pandoc进行渲染 |
因此已有的2种方案都不适合『神马笔记』。
最终的解决方案将LaTex或MathML包含在<p>
和</p>
之间。
因为marked
不会对内嵌的html代码进行转义,这样保证了原始的内容。
4. 新的问题
将包含<p>
和</p>
的方程公式保存为markdown后。
使用Typora无法直接预览方程公式,因为被处理成内嵌的HTML代码。
因此需要分成2份Markdown进行输出。
Markdown输出类型 | 处理方式 |
---|---|
正文 | 方程公式不包含<p> 和</p>
|
预览 | 将方程公式包含在<p> 和</p> 中 |
5. 再次遇到问题
使用『神马笔记』导出markdown后,使用Hexo引擎发布到个人博客时,发现很多公式不能正常显示。
因为Hexo使用marked渲染markdown文件,导致LaTeX被错误转义。
修改的方式有2种。
序号 | 方案 | 参考资料 |
---|---|---|
1 | 修改marked | Hexo下mathjax的转义问题 |
2 | 修改markdown渲染引擎为pandoc | Hexo下mathjax的转义问题 |
最终选择pandoc方式。
比较修改引擎的方式,更偏向于替换整个引擎。
五、接下来
『神马笔记』在笔记中插入方程公式元素功能开发到此结束。
接下来2件事情要做:
- 编写LaTex数学公式简明手册
- 发布新版本『神马笔记』
六、Finally
~燕雁无心~太湖西畔随云去~