Improve inline preview

- Uses commonly defined regexp
- Removed dependency on gnome-web-photo, use WebView instead
- Improved lexicon rendering
github/fork/yochananmarqos/patch-1
Gonçalo Silva 2019-06-06 02:57:43 +01:00
parent 8e97b7ae2c
commit eec633437b
6 changed files with 255 additions and 847 deletions

View File

@ -117,63 +117,37 @@
background-color: mix(@theme_base_color, @theme_bg_color, 0.5);
}
#PreviewMenuItem image {
border-radius: 2px;
color: #666;
padding: 3px 5px;
border: none;
background: #FFF;
}
.uberwriter-window treeview {
padding: 3px 3px 3px 3px;
padding: 4px 4px 4px 4px;
}
#LexikonBubble {
/*font: serif 10;*/
font-family: serif;
font-size: 10px;
background: @theme_bg_color;
border-radius: 4px;
border-color: @theme_bg_color;
margin: 5px;
padding: 5px;
}
/* .quick-preview-popup {
padding: 5px;
margin: 5px;
border: 1px solid #333;
background: @ligth_bg;
border-radius: 3px;
border-color: @theme_bg_color;
} */
#LexikonBubble label {
/*padding: 5px;*/
}
#LexikonBubble {
background-color: @theme_bg_color;
border: 5px solid @theme_bg_color;
}
#LexikonBubble .lexikon-heading {
.lexikon {
font-family: serif;
font-size: 12px;
padding-bottom: 5px;
padding-top: 5px;
font-weight: bold;
padding-left: 10px;
background: @theme_bg_color;
border: 4px solid @theme_bg_color;
}
#LexikonBubble .lexikon-num {
padding-right: 5px;
padding-left: 20px;
.lexikon .header {
font-family: serif;
font-size: 14px;
padding-top: 16px;
padding-bottom: 4px;
font-weight: bold;
}
.lexikon .header.first {
padding-top: 0px;
}
.lexikon .number {
padding-left: 16px;
padding-right: 4px;
}
.quick-preview-popup {
background-color: @theme_bg_color;
padding: 8px 12px 8px 12px;
}
.quick-preview-popup grid {

View File

@ -1,121 +0,0 @@
{
"id": "de.wolfvollprecht.UberWriter.Plugin.WebPhoto",
"runtime": "de.wolfvollprecht.UberWriter",
"branch": "stable",
"sdk": "org.gnome.Sdk//3.26",
"build-extension": true,
"separate-locales": false,
"appstream-compose": false,
"finish-args": [
],
"build-options" : {
"prefix": "/app/extensions/WebPhoto",
"env": {
"PATH": "/app/extensions/TexLive/bin:/app/extensions/TexLive/2018/bin/x86_64-linux:/app/bin:/usr/bin"
}
},
"cleanup": [],
"modules": [
{
"name": "Glib2",
"sources": [
{
"type": "archive",
"url": "http://ftp.gnome.org/pub/gnome/sources/glib/2.56/glib-2.56.1.tar.xz",
"sha256": "40ef3f44f2c651c7a31aedee44259809b6f03d3d20be44545cd7d177221c0b8d"
}
]
},
{
"name": "LibIDL",
"buildsystem": "autotools",
"sources": [
{
"type": "git",
"url": "https://github.com/GNOME/libIDL/",
"tag": "LIBIDL_0_8_14",
"commit": "666fcbf086fb859738b67417c99a9895bb3d8ce5"
}
]
},
{
"name": "ORBit2",
"rm-configure": true,
"config-opts": ["--prefix=/app/extensions/WebPhoto"],
"build-options": {
"env":{
"PKG_CONFIG_PATH": "/app/extensions/WebPhoto/lib/pkgconfig",
"GNOME2_DIR": "/app/extensions/WebPhoto",
"LD_LIBRARY_PATH": "/app/extensions/WebPhoto/lib",
"PATH": "/app/extensions/WebPhoto/bin:/usr/bin"
}
},
"sources": [
{
"type": "archive",
"url": "http://ftp.gnome.org/pub/gnome/sources/ORBit2/2.14/ORBit2-2.14.19.tar.bz2",
"sha256": "55c900a905482992730f575f3eef34d50bda717c197c97c08fa5a6eafd857550"
},
{
"type": "patch",
"path": "ORBit2.patch"
},
{
"type": "script",
"dest-filename": "autogen.sh",
"commands": [
"autoreconf -fi"
]
}
]
},
{
"name": "gconf",
"buildsystem": "autotools",
"config-opts": ["--prefix=/app/extensions/WebPhoto"],
"build-options": {
"env":{
"PKG_CONFIG_PATH": "/app/extensions/WebPhoto/lib/pkgconfig",
"GNOME2_DIR": "/app/extensions/WebPhoto",
"LD_LIBRARY_PATH": "/app/extensions/WebPhoto/lib",
"PATH": "/app/extensions/WebPhoto/bin:/usr/bin"
}
},
"sources": [
{
"type": "archive",
"url": "http://ftp.gnome.org/pub/GNOME/sources/GConf/3.2/GConf-3.2.6.tar.xz",
"sha256": "1912b91803ab09a5eed34d364bf09fe3a2a9c96751fde03a4e0cfa51a04d784c"
}
]
},
{
"name": "gnome-web-photo",
"buildsystem": "autotools",
"config-opts": [
"--with-gtk=3.0",
"--prefix=/app/extensions/WebPhoto"
],
"build-options": {
"env":{
"LD_LIBRARY_PATH": "/app/extensions/WebPhoto/lib",
"PATH": "/app/bin:/app/extensions/WebPhoto/bin:/usr/bin",
"ACLOCAL_PATH": "/app/extensions/WebPhoto/share/aclocal"
}
},
"sources": [
{
"type": "git",
"url": "https://github.com/GNOME/gnome-web-photo/",
"tag": "0.10.6",
"commit": "827d6b98c120b4dd8d689a1faf52450685ca6d46"
},
{
"type": "patch",
"path": "GnomeWebPhoto.patch"
}
]
}
]
}

View File

@ -14,101 +14,72 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
# END LICENSE
import logging
import re
import subprocess
import telnetlib
import tempfile
import threading
import urllib
import webbrowser
from gettext import gettext as _
from urllib.error import URLError
from urllib.parse import unquote
import gi
from uberwriter.text_view_markup_handler import MarkupHandler
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkPixbuf
gi.require_version("Gtk", "3.0")
gi.require_version("WebKit2", "4.0")
from gi.repository import Gtk, Gdk, GdkPixbuf, GLib
from gi.repository import WebKit2
from uberwriter import latex_to_PNG, markup_regex
from uberwriter.settings import Settings
from uberwriter.fix_table import FixTable
LOGGER = logging.getLogger('uberwriter')
class DictAccessor:
reEndResponse = re.compile(br"^[2-5][0-58][0-9] .*\r\n$", re.DOTALL + re.MULTILINE)
reDefinition = re.compile(br"^151(.*?)^\.", re.DOTALL + re.MULTILINE)
# TODO:
# - Don't insert a span with id, it breaks the text to often
# Would be better to search for the nearest title and generate
# A jumping URL from that (for preview)
# Also, after going to preview, set cursor back to where it was
class DictAccessor():
def __init__(self, host='pan.alephnull.com', port=2628, timeout=60):
def __init__(self, host="pan.alephnull.com", port=2628, timeout=60):
self.telnet = telnetlib.Telnet(host, port)
self.timeout = timeout
self.login_response = self.telnet.expect(
[self.reEndResponse], self.timeout)[2]
def get_online(self, word):
process = subprocess.Popen(['dict', '-d', 'wn', word],
stdout=subprocess.PIPE)
return process.communicate()[0]
self.login_response = self.telnet.expect([self.reEndResponse], self.timeout)[2]
def run_command(self, cmd):
self.telnet.write(cmd.encode('utf-8') + b'\r\n')
self.telnet.write(cmd.encode("utf-8") + b"\r\n")
return self.telnet.expect([self.reEndResponse], self.timeout)[2]
def get_matches(self, database, strategy, word):
if database in ['', 'all']:
d = '*'
if database in ["", "all"]:
d = "*"
else:
d = database
if strategy in ['', 'default']:
s = '.'
if strategy in ["", "default"]:
s = "."
else:
s = strategy
w = word.replace('"', r'\"')
tsplit = self.run_command('MATCH %s %s "%s"' % (d, s, w)).splitlines()
w = word.replace("\"", r"\\\"")
tsplit = self.run_command("MATCH {} {} \"{}\"".format(d, s, w)).splitlines()
mlist = list()
if tsplit[-1].startswith(b'250 ok') and tsplit[0].startswith(b'1'):
if tsplit[-1].startswith(b"250 ok") and tsplit[0].startswith(b"1"):
mlines = tsplit[1:-2]
for line in mlines:
lsplit = line.strip().split()
db = lsplit[0]
word = unquote(' '.join(lsplit[1:]))
word = unquote(" ".join(lsplit[1:]))
mlist.append((db, word))
return mlist
reEndResponse = re.compile(
br'^[2-5][0-58][0-9] .*\r\n$', re.DOTALL + re.MULTILINE)
reDefinition = re.compile(br'^151(.*?)^\.', re.DOTALL + re.MULTILINE)
def get_definition(self, database, word):
if database in ['', 'all']:
d = '*'
if database in ["", "all"]:
d = "*"
else:
d = database
w = word.replace('"', r'\"')
dsplit = self.run_command('DEFINE %s "%s"' % (d, w)).splitlines(True)
# dsplit = self.getOnline(word).splitlines()
w = word.replace("\"", r"\\\"")
dsplit = self.run_command("DEFINE {} \"{}\"".format(d, w)).splitlines(True)
dlist = list()
if dsplit[-1].startswith(b'250 ok') and dsplit[0].startswith(b'1'):
if dsplit[-1].startswith(b"250 ok") and dsplit[0].startswith(b"1"):
dlines = dsplit[1:-1]
dtext = b''.join(dlines)
dlist = self.reDefinition.findall(dtext)
# print(dlist)
dtext = b"".join(dlines)
dlist = [dtext]
# dlist = dsplit # not using the localhost telnet connection
return dlist
def close(self):
t = self.run_command('QUIT')
t = self.run_command("QUIT")
self.telnet.close()
return t
@ -118,412 +89,210 @@ class DictAccessor():
lines = response.splitlines()
lines = lines[2:]
lines = ' '.join(lines)
lines = re.sub(r'\s+', ' ', lines).strip()
lines = re.split(r'( adv | adj | n | v |^adv |^adj |^n |^v )', lines)
lines = " ".join(lines)
lines = re.sub(r"\s+", " ", lines).strip()
lines = re.split(r"( adv | adj | n | v |^adv |^adj |^n |^v )", lines)
res = []
act_res = {'defs': [], 'class': 'none', 'num': 'None'}
act_res = {"defs": [], "class": "none", "num": "None"}
for l in lines:
l = l.strip()
if not l:
continue
if l in ['adv', 'adj', 'n', 'v']:
if l in ["adv", "adj", "n", "v"]:
if act_res:
res.append(act_res.copy())
act_res = {}
act_res['defs'] = []
act_res['class'] = l
act_res = {"defs": [], "class": l}
else:
ll = re.split(r'(?: |^)(\d): ', l)
ll = re.split(r"(?: |^)(\d): ", l)
act_def = {}
for lll in ll:
if lll.strip().isdigit() or not lll.strip():
if 'description' in act_def and act_def['description']:
act_res['defs'].append(act_def.copy())
act_def = {'num': lll}
if "description" in act_def and act_def["description"]:
act_res["defs"].append(act_def.copy())
act_def = {"num": lll}
continue
a = re.findall(r'(\[(syn|ant): (.+?)\] ??)+', lll)
a = re.findall(r"(\[(syn|ant): (.+?)\] ??)+", lll)
for n in a:
if n[1] == 'syn':
act_def['syn'] = re.findall(r'\{(.*?)\}.*?', n[2])
if n[1] == "syn":
act_def["syn"] = re.findall(r"{(.*?)}.*?", n[2])
else:
act_def['ant'] = re.findall(r'\{(.*?)\}.*?', n[2])
tbr = re.search(r'\[.+\]', lll)
act_def["ant"] = re.findall(r"{(.*?)}.*?", n[2])
tbr = re.search(r"\[.+\]", lll)
if tbr:
lll = lll[:tbr.start()]
lll = lll.split(';')
act_def['examples'] = []
act_def['description'] = []
lll = lll.split(";")
act_def["examples"] = []
act_def["description"] = []
for llll in lll:
llll = llll.strip()
if llll.strip().startswith('"'):
act_def['examples'].append(llll)
if llll.strip().startswith("\""):
act_def["examples"].append(llll)
else:
act_def['description'].append(llll)
act_def["description"].append(llll)
if act_def and "description" in act_def:
act_res["defs"].append(act_def.copy())
if act_def and 'description' in act_def:
act_res['defs'].append(act_def.copy())
# pprint(act_res)
res.append(act_res.copy())
return res
def check_url(url, item, spinner):
LOGGER.debug("thread started, checking url")
error = False
try:
response = urllib.request.urlopen(url)
except URLError as e:
error = True
text = "Error! Reason: %s" % e.reason
if not error:
if response.code >= 400:
LOGGER.debug("Website not available")
text = _("Website is not available")
else:
text = _("Website is available")
LOGGER.debug("Response: %s" % text)
spinner.destroy()
item.set_label(text)
def get_dictionary(term):
da = DictAccessor()
output = da.get_definition('wn', term)
output = da.get_definition("wn", term)
if output:
output = output[0]
else:
return None
return da.parse_wordnet(output.decode(encoding='UTF-8'))
def get_web_thumbnail(url, item, spinner):
LOGGER.debug("thread started, generating thumb")
# error = False
# gnome-web-photo only understands http urls
if url.startswith("www"):
url = "http://" + url
filename = tempfile.mktemp(suffix='.png')
thumb_size = '256' # size can only be 32, 64, 96, 128 or 256!
args = ['gnome-web-photo', '--mode=thumbnail',
'-s', thumb_size, url, filename]
process = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
_output = process.communicate()[0]
image = Gtk.Image.new_from_file(filename)
image.show()
# if not error:
# if (response.code / 100) >= 4:
# logger.debug("Website not available")
# text = _("Website is not available")
# else:
# text = _("Website is available")
spinner.destroy()
item.add(image)
item.show()
def fill_lexikon_bubble(vocab, lexikon_dict):
grid = Gtk.Grid.new()
i = 0
grid.set_name('LexikonBubble')
grid.set_row_spacing(2)
grid.set_column_spacing(4)
if lexikon_dict:
for entry in lexikon_dict:
vocab_label = Gtk.Label.new(vocab + ' ~ ' + entry['class'])
vocab_label.get_style_context().add_class('lexikon-heading')
vocab_label.set_halign(Gtk.Align.START)
vocab_label.set_justify(Gtk.Justification.LEFT)
grid.attach(vocab_label, 0, i, 3, 1)
for definition in entry['defs']:
i = i + 1
num_label = Gtk.Label.new(definition['num'])
num_label.get_style_context().add_class('lexikon-num')
num_label.set_justify(Gtk.Justification.RIGHT)
grid.attach(num_label, 0, i, 1, 1)
def_label = Gtk.Label.new(' '.join(definition['description']))
def_label.set_halign(Gtk.Align.START)
def_label.set_justify(Gtk.Justification.LEFT)
def_label.get_style_context().add_class('lexikon-definition')
def_label.props.wrap = True
grid.attach(def_label, 1, i, 1, 1)
i = i + 1
grid.show_all()
return grid
return None
return da.parse_wordnet(output.decode(encoding="UTF-8"))
class InlinePreview:
WIDTH = 400
HEIGHT = 300
def __init__(self, text_view):
self.text_view = text_view
self.text_view.connect("button-press-event", self.on_button_press_event)
self.text_buffer = text_view.get_buffer()
self.cursor_mark = self.text_buffer.create_mark(
"click", self.text_buffer.get_iter_at_mark(self.text_buffer.get_insert()))
self.latex_converter = latex_to_PNG.LatexToPNG()
cursor_mark = self.text_buffer.get_insert()
cursor_iter = self.text_buffer.get_iter_at_mark(cursor_mark)
self.click_mark = self.text_buffer.create_mark('click', cursor_iter)
# Events for popup menu
# self.TextView.connect_after('populate-popup', self.populate_popup)
# self.TextView.connect_after('popup-menu', self.move_popup)
self.text_view.connect('button-press-event', self.click_move_button)
self.popover = None
self.settings = Settings.new()
self.characters_per_line = Settings.new().get_int("characters-per-line")
def open_popover_with_widget(self, widget):
a = self.text_buffer.create_child_anchor(
self.text_buffer.get_iter_at_mark(self.click_mark))
lbl = Gtk.Label('')
self.text_view.add_child_at_anchor(lbl, a)
lbl.show()
# a = Gtk.Window.new(Gtk.WindowType.POPUP)
# a.set_transient_for(self.TextView.get_toplevel())
# a.grab_focus()
# a.set_name("QuickPreviewPopup")
# # a.set_attached_to(self.TextView)
# a.move(300, 300)
# a.set_modal(True)
# def close(widget, event, *args):
# if(event.keyval == Gdk.KEY_Escape):
# widget.destroy()
# a.connect('key-press-event', close)
alignment = Gtk.Alignment()
alignment.props.margin_bottom = 5
alignment.props.margin_top = 5
alignment.props.margin_left = 4
alignment.add(widget)
# self.TextView.add_child_in_window(b, Gtk.TextWindowType.WIDGET, 200, 200)
# b.attach(Gtk.Label.new("test 123"), 0, 0, 1, 1)
# b.show_all()
# a.show_all()
self.popover = Gtk.Popover.new(lbl)
self.popover = Gtk.Popover.new(self.text_view)
self.popover.get_style_context().add_class("quick-preview-popup")
self.popover.add(alignment)
# a.add(alignment)
_dismiss, rect = self.popover.get_pointing_to()
rect.y = rect.y - 20
self.popover.set_pointing_to(rect)
# widget = Gtk.Label.new("testasds a;12j3 21 lk3j213")
widget.show_all()
# b.attach(widget, 0, 1, 1, 1)
self.popover.set_modal(True)
self.popover.show_all()
# print(self.popover)
self.popover.set_property('width-request', 50)
def click_move_button(self, _widget, event):
self.preview_fns = {
markup_regex.MATH: self.get_view_for_math,
markup_regex.IMAGE: self.get_view_for_image,
markup_regex.LINK: self.get_view_for_link,
markup_regex.FOOTNOTE_ID: self.get_view_for_footnote,
re.compile(r"(?P<text>\w+)"): self.get_view_for_lexikon
}
def on_button_press_event(self, _text_view, event):
if event.button == 1 and event.state & Gdk.ModifierType.CONTROL_MASK:
x, y = self.text_view.window_to_buffer_coords(2,
int(event.x),
int(event.y))
self.text_buffer.move_mark(self.click_mark,
self.text_view.get_iter_at_location(x, y).iter)
self.populate_popup(self.text_view)
x, y = self.text_view.window_to_buffer_coords(2, int(event.x), int(event.y))
self.text_buffer.move_mark(
self.cursor_mark, self.text_view.get_iter_at_location(x, y).iter)
self.open_popover(self.text_view)
def fix_table(self, _widget, _data=None):
LOGGER.debug('fixing that table')
fix_table = FixTable(self.text_buffer)
fix_table.fix_table()
def get_view_for_math(self, match):
success, result = self.latex_converter.generatepng(match.group("text"))
if success:
return Gtk.Image.new_from_file(result)
else:
error = _("Formula looks incorrect:")
error += "\n\n{}".format(result)
return Gtk.Label(label=error)
def populate_popup(self, _editor, _data=None):
# popover = Gtk.Popover.new(editor)
# pop_cont = Gtk.Container.new()
# popover.add(pop_cont)
# popover.show_all()
def get_view_for_image(self, match):
path = match.group("url")
if not path.startswith(("file://", "/")):
return self.get_view_for_link(match)
elif path.startswith("file://"):
path = path[7:]
return Gtk.Image.new_from_pixbuf(
GdkPixbuf.Pixbuf.new_from_file_at_size(path, self.WIDTH, self.HEIGHT))
item = Gtk.MenuItem.new()
item.set_name("PreviewMenuItem")
separator = Gtk.SeparatorMenuItem.new()
def get_view_for_link(self, match):
url = match.group("url")
web_view = WebKit2.WebView(zoom_level=0.3125) # ~1280x960
web_view.set_size_request(self.WIDTH, self.HEIGHT)
if GLib.uri_parse_scheme(url) is None:
url = "http://{}".format(url)
web_view.load_uri(url)
return web_view
# table_item = Gtk.MenuItem.new()
# table_item.set_label('Fix that table')
def get_view_for_footnote(self, match):
footnote_id = match.group("id")
fn_matches = re.finditer(markup_regex.FOOTNOTE, self.text_buffer.props.text)
for fn_match in fn_matches:
if fn_match.group("id") == footnote_id:
if fn_match:
footnote = re.sub("\n[\t ]+", "\n", fn_match.group("text"))
else:
footnote = _("No matching footnote found")
label = Gtk.Label(label=footnote)
label.set_max_width_chars(self.characters_per_line)
label.set_line_wrap(True)
return label
return None
# table_item.connect('activate', self.fix_table)
# table_item.show()
# menu.prepend(table_item)
# menu.show()
def get_view_for_lexikon(self, match):
term = match.group("text")
lexikon_dict = get_dictionary(term)
if lexikon_dict:
grid = Gtk.Grid.new()
grid.get_style_context().add_class("lexikon")
grid.set_row_spacing(2)
grid.set_column_spacing(4)
i = 0
for entry in lexikon_dict:
if not entry["defs"]:
continue
elif entry["class"].startswith("n"):
word_type = _("noun")
elif entry["class"].startswith("v"):
word_type = _("verb")
elif entry["class"].startswith("adj"):
word_type = _("adjective")
elif entry["class"].startswith("adv"):
word_type = _("adverb")
else:
continue
start_iter = self.text_buffer.get_iter_at_mark(self.click_mark)
# Line offset of click mark
vocab_label = Gtk.Label.new(term + " ~ " + word_type)
vocab_label.get_style_context().add_class("header")
if i == 0:
vocab_label.get_style_context().add_class("first")
vocab_label.set_halign(Gtk.Align.START)
vocab_label.set_justify(Gtk.Justification.LEFT)
grid.attach(vocab_label, 0, i, 3, 1)
for definition in entry["defs"]:
i = i + 1
num_label = Gtk.Label.new(definition["num"] + ".")
num_label.get_style_context().add_class("number")
num_label.set_valign(Gtk.Align.START)
grid.attach(num_label, 0, i, 1, 1)
def_label = Gtk.Label(label=" ".join(definition["description"]))
def_label.get_style_context().add_class("description")
def_label.set_halign(Gtk.Align.START)
def_label.set_max_width_chars(self.characters_per_line)
def_label.set_line_wrap(True)
def_label.set_justify(Gtk.Justification.FILL)
grid.attach(def_label, 1, i, 1, 1)
i = i + 1
if i > 0:
return grid
return None
def open_popover(self, _editor, _data=None):
start_iter = self.text_buffer.get_iter_at_mark(self.cursor_mark)
line_offset = start_iter.get_line_offset()
end_iter = start_iter.copy()
start_iter.set_line_offset(0)
end_iter.forward_to_line_end()
text = self.text_buffer.get_text(start_iter, end_iter, False)
footnote = re.compile(r'\[\^([^\s]+?)\]')
image = re.compile(r"!\[(.*?)\]\((.+?)\)")
found_match = False
matches = re.finditer(markup_regex.MATH, text)
for match in matches:
LOGGER.debug(match.group(1))
if match.start() < line_offset < match.end():
success, result = self.latex_converter.generatepng(
match.group(1))
if success:
image = Gtk.Image.new_from_file(result)
image.show()
LOGGER.debug("logging image")
# item.add(image)
self.open_popover_with_widget(image)
else:
label = Gtk.Label()
msg = 'Formula looks incorrect:\n' + result
label.set_alignment(0.0, 0.5)
label.set_text(msg)
label.show()
item.add(label)
self.open_popover_with_widget(item)
item.show()
# menu.prepend(separator)
# separator.show()
# menu.prepend(item)
# menu.show()
found_match = True
break
if not found_match:
# Links
matches = re.finditer(markup_regex.LINK, text)
for regex, get_view_fn in self.preview_fns.items():
matches = re.finditer(regex, text)
for match in matches:
if match.start() < line_offset < match.end():
text = text[text.find("http://"):-1]
item.connect("activate", lambda w: webbrowser.open(text))
LOGGER.debug(text)
statusitem = Gtk.MenuItem.new()
statusitem.show()
spinner = Gtk.Spinner.new()
spinner.start()
statusitem.add(spinner)
spinner.show()
thread = threading.Thread(target=check_url,
args=(text, statusitem, spinner))
thread.start()
webphoto_item = Gtk.MenuItem.new()
webphoto_item.show()
spinner_2 = Gtk.Spinner.new()
spinner_2.start()
webphoto_item.add(spinner_2)
spinner_2.show()
thread_image = threading.Thread(target=get_web_thumbnail,
args=(text, webphoto_item, spinner_2))
thread_image.start()
item.set_label(_("Open Link in Webbrowser"))
item.show()
self.open_popover_with_widget(webphoto_item)
# menu.prepend(separator)
# separator.show()
# menu.prepend(webphoto_item)
# menu.prepend(statusitem)
# menu.prepend(item)
# menu.show()
found_match = True
break
if not found_match:
matches = re.finditer(image, text)
for match in matches:
if match.start() < line_offset < match.end():
path = match.group(2)
if path.startswith("file://"):
path = path[7:]
elif not path.startswith("/"):
# then the path is relative
base_path = self.settings.get_string("open-file-path")
path = base_path + "/" + path
LOGGER.info(path)
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path, 400, 300)
image = Gtk.Image.new_from_pixbuf(pixbuf)
image.show()
self.open_popover_with_widget(image)
item.set_property('width-request', 50)
# item.add(image)
# item.set_property('width-request', 50)
# item.show()
# menu.prepend(separator)
# separator.show()
# menu.prepend(item)
# menu.show()
found_match = True
break
if not found_match:
matches = re.finditer(footnote, text)
for match in matches:
if match.start() < line_offset < match.end():
LOGGER.debug(match.group(1))
footnote_match = re.compile(
r"\[\^" + match.group(1) + r"\]: (.+(?:\n|\Z)(?:^[\t].+(?:\n|\Z))*)",
re.MULTILINE)
replace = re.compile(r"^\t", re.MULTILINE)
start, end = self.text_buffer.get_bounds()
fn_match = re.search(
footnote_match, self.text_buffer.get_text(start, end, False))
label = Gtk.Label()
label.set_alignment(0.0, 0.5)
LOGGER.debug(fn_match)
if fn_match:
result = re.sub(replace, "", fn_match.group(1))
if result.endswith("\n"):
result = result[:-1]
else:
result = _("No matching footnote found")
label.set_max_width_chars(40)
label.set_line_wrap(True)
label.set_text(result)
label.show()
item.add(label)
item.show()
self.open_popover_with_widget(item)
# menu.prepend(separator)
# separator.show()
# menu.prepend(item)
# menu.show()
found_match = True
break
if not found_match:
start_iter = self.text_buffer.get_iter_at_mark(self.click_mark)
start_iter.backward_word_start()
end_iter = start_iter.copy()
end_iter.forward_word_end()
word = self.text_buffer.get_text(start_iter, end_iter, False)
terms = get_dictionary(word)
if terms:
scrolled_window = Gtk.ScrolledWindow.new()
scrolled_window.add(fill_lexikon_bubble(word, terms))
scrolled_window.props.width_request = 500
scrolled_window.props.height_request = 400
scrolled_window.show_all()
self.open_popover_with_widget(scrolled_window)
def move_popup(self):
pass
if match.start() <= line_offset <= match.end():
prev_view = self.popover.get_child()
if prev_view:
prev_view.destroy()
view = get_view_fn(match)
view.show_all()
self.popover.add(view)
rect = self.text_view.get_iter_location(
self.text_buffer.get_iter_at_mark(self.cursor_mark))
rect.x, rect.y = self.text_view.buffer_to_window_coords(
Gtk.TextWindowType.TEXT, rect.x, rect.y)
self.popover.set_pointing_to(rect)
GLib.idle_add(self.popover.popup) # TODO: It doesn't popup without idle_add.
return

View File

@ -3,61 +3,59 @@
Based on latex2png.py from Stuart Rackham
AUTHOR
Written by Stuart Rackham, <srackham@gmail.com>
The code was inspired by Kjell Magne Fauske's code:
http://fauskes.net/nb/htmleqII/
Written by Stuart Rackham, <srackham@gmail.com>
The code was inspired by Kjell Magne Fauske"s code:
http://fauskes.net/nb/htmleqII/
See also:
http://www.amk.ca/python/code/mt-math
http://code.google.com/p/latexmath2png/
See also:
http://www.amk.ca/python/code/mt-math
http://code.google.com/p/latexmath2png/
COPYING
Copyright (C) 2010 Stuart Rackham. Free use of this software is
granted under the terms of the MIT License.
Copyright (C) 2010 Stuart Rackham. Free use of this software is
granted under the terms of the MIT License.
"""
import os
import sys
import tempfile
import subprocess
import tempfile
class LatexToPNG():
class LatexToPNG:
TEX_HEADER = r"""\documentclass{article}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amssymb}
\usepackage{bm}
\newcommand{\mx}[1]{\mathbf{\bm{#1}}} % Matrix command
\newcommand{\vc}[1]{\mathbf{\bm{#1}}} % Vector command
\newcommand{\T}{\text{T}} % Transpose
\pagestyle{empty}
\begin{document}"""
TEX_HEADER = r'''\documentclass{article}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amssymb}
\usepackage{bm}
\newcommand{\mx}[1]{\mathbf{\bm{#1}}} % Matrix command
\newcommand{\vc}[1]{\mathbf{\bm{#1}}} % Vector command
\newcommand{\T}{\text{T}} % Transpose
\pagestyle{empty}
\begin{document}'''
TEX_FOOTER = r'''\end{document}'''
TEX_FOOTER = r"""\end{document}"""
def __init__(self):
self.temp_result = tempfile.NamedTemporaryFile(suffix='.png')
self.temp_result = tempfile.NamedTemporaryFile(suffix=".png")
def latex2png(self, tex, outfile, dpi, modified):
'''Convert LaTeX input file infile to PNG file named outfile.'''
"""Convert LaTeX input file infile to PNG file named outfile."""
outfile = os.path.abspath(outfile)
outdir = os.path.dirname(outfile)
texfile = tempfile.mktemp(suffix='.tex', dir=os.path.dirname(outfile))
texfile = tempfile.mktemp(suffix=".tex", dir=os.path.dirname(outfile))
basefile = os.path.splitext(texfile)[0]
dvifile = basefile + '.dvi'
temps = [basefile + ext for ext in ('.tex', '.dvi', '.aux', '.log')]
dvifile = basefile + ".dvi"
temps = [basefile + ext for ext in (".tex", ".dvi", ".aux", ".log")]
skip = False
tex = '%s\n%s\n%s\n' % (self.TEX_HEADER, tex.strip(), self.TEX_FOOTER)
tex = "{}\n{}\n{}\n".format(self.TEX_HEADER, tex.strip(), self.TEX_FOOTER)
open(texfile, 'w').write(tex)
open(texfile, "w").write(tex)
saved_pwd = os.getcwd()
os.chdir(outdir)
args = ['latex', '-halt-on-error', texfile]
args = ["latex", "-halt-on-error", texfile]
p = subprocess.Popen(args,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE)
@ -66,13 +64,13 @@ class LatexToPNG():
output_lines = output.readlines()
if os.path.isfile(dvifile): # DVI File exists
# Convert DVI file to PNG.
args = ['dvipng',
'-D', str(dpi),
'-T', 'tight',
'-x', '1000',
'-z', '9',
'-bg', 'Transparent',
'-o', outfile,
args = ["dvipng",
"-D", str(dpi),
"-T", "tight",
"-x", "1000",
"-z", "9",
"-bg", "Transparent",
"-o", outfile,
dvifile]
p = subprocess.Popen(args)
@ -80,15 +78,15 @@ class LatexToPNG():
else:
self.clean_up(temps)
'''
Errors in Latex output start with "! "
Stripping exclamation marks and superflous newlines
and telling the user what he's done wrong.
'''
"""
Errors in Latex output start with "! "
Stripping exclamation marks and superflous newlines
and telling the user what he"s done wrong.
"""
i = []
error = ""
for line in output_lines:
line = line.decode('utf-8')
line = line.decode("utf-8")
if line.startswith("!"):
error += line[2:] # removing "! "
if error.endswith("\n"):
@ -97,14 +95,14 @@ class LatexToPNG():
def generatepng(self, formula):
try:
self.temp_result = tempfile.NamedTemporaryFile(suffix='.png')
self.temp_result = tempfile.NamedTemporaryFile(suffix=".png")
formula = "$" + formula + "$"
self.latex2png(formula, self.temp_result.name, 300, False)
return (True, self.temp_result.name)
return True, self.temp_result.name
except Exception as e:
self.temp_result.close()
return (False, e.args[0])
return False, e.args[0]
def clean_up(self, files):
for f in files:

View File

@ -9,22 +9,28 @@ BOLD_ITALIC = re.compile(
STRIKETHROUGH = re.compile(
r"~~(?P<text>.+?)~~")
LINK = re.compile(
r"\[(?P<text>.*)\]\((?P<url>.+?)\)")
r"\[(?P<text>.*)\]\((?P<url>.+?)(?: \"(?P<title>.+)\")?\)")
IMAGE = re.compile(
r"!\[(?P<text>.*)\]\((?P<url>.+?)(?: \"(?P<title>.+)\")?\)")
HORIZONTAL_RULE = re.compile(
r"(?:^\n*|\n\n)(?P<symbols>[ ]{0,3}[*\-_]{3,}[ ]*)(?:\n+|$)")
r"(?:^\n*|\n\n)(?P<symbols> {0,3}[*\-_]{3,} *)(?:\n+|$)")
LIST = re.compile(
r"(?:^\n*|\n\n)(?P<indent>(?:\t|[ ]{4})*)[\-*+]([ ]+)(?P<text>.+(?:\n+ \2.+)*)")
r"(?:^\n*|\n\n)(?P<indent>(?:\t| {4})*)[\-*+]( +)(?P<text>.+(?:\n+ \2.+)*)")
ORDERED_LIST = re.compile(
r"(?:^\n*|\n\n)(?P<indent>(?:\t|[ ]{4})*)(?P<prefix>(?:\d|[a-z])+[.)]) (?P<text>.+(?:\n+ {2}\2.+)*)")
r"(?:^\n*|\n\n)(?P<indent>(?:\t| {4})*)(?P<prefix>(?:\d|[a-z])+[.)]) (?P<text>.+(?:\n+ {2}\2.+)*)")
BLOCK_QUOTE = re.compile(
r"^[ ]{0,3}(?:> ?)+(?P<text>.+)", re.M)
r"^ {0,3}(?:> ?)+(?P<text>.+)", re.M)
HEADER = re.compile(
r"^[ ]{0,3}(?P<level>#{1,6}) (?P<text>[^\n]+)", re.M)
r"^ {0,3}(?P<level>#{1,6}) (?P<text>[^\n]+)", re.M)
HEADER_UNDER = re.compile(
r"(?:^\n*|\n\n)(?P<text>[^\s].+)\n[ ]{0,3}[=\-]+(?:\s+?\n|$)")
r"(?:^\n*|\n\n)(?P<text>[^\s].+)\n {0,3}[=\-]+(?:\s+?\n|$)")
CODE_BLOCK = re.compile(
r"(?:^|\n)[ ]{0,3}(?P<block>([`~]{3})(?P<text>.+?)[ ]{0,3}\2)(?:\s+?\n|$)", re.S)
r"(?:^|\n) {0,3}(?P<block>([`~]{3})(?P<text>.+?) {0,3}\2)(?:\s+?\n|$)", re.S)
TABLE = re.compile(
r"^[\-+]{5,}\n(?P<text>.+?)\n[\-+]{5,}\n", re.S)
MATH = re.compile(
r"([$]{1,2})[^` ](?P<text>.+?)[^`\\ ]\1")
FOOTNOTE_ID = re.compile(
r"[^\s]+\[\^(?P<id>[^\s]+)\]")
FOOTNOTE = re.compile(
r"(?:^\n*|\n\n)\[\^(?P<id>[^\s]+)\]: (?P<text>(?:[^\n]+|\n+(?=(?:\t| {4})))+)(?:\n+|$)", re.M)

View File

@ -1,218 +0,0 @@
#!/usr/bin/env python
# webkit2png - makes screenshots of webpages
# http://www.paulhammond.org/webkit2png
# modified and updated version by @somas95 (Manuel Genovés)
__version__=''
# Copyright (c) 2009 Paul Hammond
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import optparse
import sys
import os
try:
import gi
gi.require_version('WebKit2', '4.0')
gi.require_version('Gtk', '3.0')
from gi.repository import GObject as gobject
from gi.repository import Gtk as gtk
from gi.repository import Pango as pango
from gi.repository import WebKit2 as webkit
mode = "pygtk"
except ImportError:
print("Cannot find python-webkit library files. Are you sure they're installed?")
sys.exit()
class PyGTKBrowser:
def _save_image(self, webview, res, data):
try:
original_surface = webview.get_snapshot_finish(res)
import cairo
new_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1024, 768)
ctx = cairo.Context(new_surface)
ctx.set_source_surface(original_surface, 0, 0)
ctx.paint()
new_surface.write_to_png("test.png")
self.thumbnail = os.path.abspath("test.png")
#return new_surface
except Exception as e:
print("Could not draw thumbnail for %s: %s" % (self.title, str(e)))
def _view_load_finished_cb(self, view, event):
print(event)
if event == webkit.LoadEvent.FINISHED:
pixmap = view.get_snapshot(webkit.SnapshotRegion(1),
webkit.SnapshotOptions(0),
None, self._save_image, None)
#size = get_size()
URL = view.get_main_frame().get_uri()
filename = makeFilename(URL, self.options)
pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, size[0], size[1])
pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(),0,0,0,0,-1,-1)
if self.options.fullsize:
pixbuf.save(filename + "-full.png", "png")
if self.options.thumb or self.options.clipped:
thumbWidth = int(size[0] * self.options.scale)
thumbHeight = int(size[1] * self.options.scale)
thumbbuf = pixbuf.scale_simple(thumbWidth, thumbHeight, gtk.gdk.INTERP_BILINEAR)
if self.options.thumb:
thumbbuf.save(filename + "-thumb.png", "png")
if self.options.clipped:
clipbuf = thumbbuf.subpixbuf(0,thumbHeight-int(self.options.clipheight),
int(self.options.clipwidth),
int(self.options.clipheight))
clipbuf.save(filename + "-clip.png", "png")
gtk.main_quit()
def __init__(self, options, args):
self.options = options
if options.delay:
print("--delay is only supported on Mac OS X (for now). Sorry!")
window = gtk.Window()
window.resize(int(options.initWidth),int(options.initHeight))
self.view = webkit.WebView()
settings = self.view.get_settings()
settings.set_property("auto-load-images", not options.noimages)
self.view.set_settings(settings)
self.view.connect("load_changed", self._view_load_finished_cb)
# window.add(self.view)
# window.show_all()
self.view.load_uri(args[0])
# go go go
gtk.main()
def main():
# parse the command line
usage = """%prog [options] [http://example.net/ ...]
examples:
%prog http://google.com/ # screengrab google
%prog -W 1000 -H 1000 http://google.com/ # bigger screengrab of google
%prog -T http://google.com/ # just the thumbnail screengrab
%prog -TF http://google.com/ # just thumbnail and fullsize grab
%prog -o foo http://google.com/ # save images as "foo-thumb.png" etc
%prog - # screengrab urls from stdin
%prog -h | less # full documentation"""
cmdparser = optparse.OptionParser(usage,version=("webkit2png "+__version__))
# TODO: add quiet/verbose options
cmdparser.add_option("-W", "--width",type="float",default=800.0,
help="initial (and minimum) width of browser (default: 800)")
cmdparser.add_option("-H", "--height",type="float",default=600.0,
help="initial (and minimum) height of browser (default: 600)")
cmdparser.add_option("--clipwidth",type="float",default=200.0,
help="width of clipped thumbnail (default: 200)",
metavar="WIDTH")
cmdparser.add_option("--clipheight",type="float",default=150.0,
help="height of clipped thumbnail (default: 150)",
metavar="HEIGHT")
cmdparser.add_option("-s", "--scale",type="float",default=0.25,
help="scale factor for thumbnails (default: 0.25)")
cmdparser.add_option("-m", "--md5", action="store_true",
help="use md5 hash for filename (like del.icio.us)")
cmdparser.add_option("-o", "--filename", type="string",default="",
metavar="NAME", help="save images as NAME-full.png,NAME-thumb.png etc")
cmdparser.add_option("-F", "--fullsize", action="store_true",
help="only create fullsize screenshot")
cmdparser.add_option("-T", "--thumb", action="store_true",
help="only create thumbnail sreenshot")
cmdparser.add_option("-C", "--clipped", action="store_true",
help="only create clipped thumbnail screenshot")
cmdparser.add_option("-d", "--datestamp", action="store_true",
help="include date in filename")
cmdparser.add_option("-D", "--dir",type="string",default="./",
help="directory to place images into")
cmdparser.add_option("--delay",type="float",default=0,
help="delay between page load finishing and screenshot")
cmdparser.add_option("--noimages", action="store_true",
help="don't load images")
cmdparser.add_option("--debug", action="store_true",
help=optparse.SUPPRESS_HELP)
(options, args) = cmdparser.parse_args()
if len(args) == 0:
cmdparser.print_usage()
return
if options.filename:
if len(args) != 1 or args[0] == "-":
print("--filename option requires exactly one url")
return
if options.scale == 0:
cmdparser.error("scale cannot be zero")
# make sure we're outputing something
if not (options.fullsize or options.thumb or options.clipped):
options.fullsize = True
options.thumb = True
options.clipped = True
# work out the initial size of the browser window
# (this might need to be larger so clipped image is right size)
options.initWidth = (options.clipwidth / options.scale)
options.initHeight = (options.clipheight / options.scale)
if options.width>options.initWidth:
options.initWidth = options.width
if options.height>options.initHeight:
options.initHeight = options.height
PyGTKBrowser(options, args)
def makeFilename(self, URL, options):
# make the filename
if options.filename:
filename = options.filename
elif options.md5:
try:
import md5
except ImportError:
print("--md5 requires python md5 library")
filename = md5.new(URL).hexdigest()
else:
import re
filename = re.sub('\W','',URL)
filename = re.sub('^http','',filename)
if options.datestamp:
import time
now = time.strftime("%Y%m%d")
filename = now + "-" + filename
import os
dir = os.path.abspath(os.path.expanduser(options.dir))
return os.path.join(dir,filename)
if __name__ == "__main__": main()