forked from Mirrors/apostrophe
Improve inline preview
- Uses commonly defined regexp - Removed dependency on gnome-web-photo, use WebView instead - Improved lexicon renderinggithub/fork/yochananmarqos/patch-1
parent
8e97b7ae2c
commit
eec633437b
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue