ipe-7.2.13/ 0000755 0001750 0001750 00000000000 13561570220 012234 5 ustar otfried otfried ipe-7.2.13/src/ 0000755 0001750 0001750 00000000000 13561570220 013023 5 ustar otfried otfried ipe-7.2.13/src/ipeui/ 0000755 0001750 0001750 00000000000 13561570220 014136 5 ustar otfried otfried ipe-7.2.13/src/ipeui/ipeui_cocoa.cpp 0000644 0001750 0001750 00000107312 13561570220 017125 0 ustar otfried otfried // -*- objc -*-
// --------------------------------------------------------------------
// Lua bindings for Cocoa dialogs
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifdef IPESTRICT
#include "../include/ipeosx.h"
#endif
#include "ipeui_common.h"
#import
#include "ipeuilayout_cocoa.h"
#include
#define COLORICONSIZE 12
inline const char *N2C(NSString *aStr) { return aStr.UTF8String; }
inline NSString *C2N(const char *s) {return [NSString stringWithUTF8String:s];}
inline NSString *S2N(const std::string &s) { return C2N(s.c_str()); }
// --------------------------------------------------------------------
class PDialog;
@interface IpeDialogDelegate : NSObject
@property PDialog *dialog;
- (void) ipeControl:(id) sender;
@end
// --------------------------------------------------------------------
void addToLayout(NSView *view, NSView *subview)
{
[view addSubview:subview];
[subview setTranslatesAutoresizingMaskIntoConstraints:NO];
}
id layoutGuide(NSView *owner)
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
if ([owner respondsToSelector:@selector(addLayoutGuide:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSLayoutGuide *g = [[NSLayoutGuide alloc] init];
[owner addLayoutGuide:g];
#pragma clang diagnostic pop
return g;
}
#endif
NSView *g = [[NSView alloc] initWithFrame:NSZeroRect];
addToLayout(owner, g);
return g;
}
static NSLayoutAttribute layoutAttribute(char ch)
{
switch (ch) {
case 'l':
return NSLayoutAttributeLeft;
case 'r':
return NSLayoutAttributeRight;
case 't':
return NSLayoutAttributeTop;
case 'b':
return NSLayoutAttributeBottom;
case 'w':
return NSLayoutAttributeWidth;
case 'h':
return NSLayoutAttributeHeight;
case 'x':
return NSLayoutAttributeCenterX;
case 'y':
return NSLayoutAttributeCenterY;
default:
return NSLayoutAttributeNotAnAttribute;
}
}
static NSLayoutRelation layoutRelation(char ch)
{
switch (ch) {
case '<':
return NSLayoutRelationLessThanOrEqual;
case '=':
default:
return NSLayoutRelationEqual;
case '>':
return NSLayoutRelationGreaterThanOrEqual;
}
}
static NSView *owner(id a)
{
if ([a respondsToSelector:@selector(superview)])
return [a superview];
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
if ([a respondsToSelector:@selector(owningView)])
return [a owningView]; // is a layout guide
#endif
return nil;
}
void activateConstraint(NSLayoutConstraint *c, BOOL active)
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
if ([c respondsToSelector:@selector(setActive:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
c.active = active;
#pragma clang diagnostic pop
return;
}
#endif
// need to figure out lowest common ancestor
id a = c.firstItem;
id b = c.secondItem;
NSView *lca = nil;
NSView *ao = owner(a);
NSView *bo = owner(b);
if (b == nil)
lca = a;
else if (ao == b)
lca = b;
else if (bo == a)
lca = a;
else {
assert(ao == bo);
lca = ao;
}
if (active)
[lca addConstraint:c];
else
[lca removeConstraint:c];
}
NSLayoutConstraint *layout(id a, id b, const char *rel, double gap,
double multiplier, BOOL activate)
{
assert(strlen(rel) == 3);
NSLayoutAttribute a1 = layoutAttribute(rel[0]);
NSLayoutAttribute b1 = layoutAttribute(rel[2]);
NSLayoutConstraint *c =
[NSLayoutConstraint
constraintWithItem:a
attribute:a1
relatedBy:layoutRelation(rel[1])
toItem:b
attribute:b1
multiplier:multiplier
constant:gap];
if (activate)
activateConstraint(c, YES);
return c;
}
// --------------------------------------------------------------------
// Exported from ipeui!
NSString *ipeui_set_mnemonic(NSString *title, NSButton *button)
{
unichar mnemonic = 0;
NSMutableString *result = [NSMutableString stringWithCapacity:[title length]];
size_t i = 0;
while (i < [title length]) {
unichar ch = [title characterAtIndex:i];
if (ch != '&') {
[result appendFormat:@"%C", ch];
++i;
} else if (i + 1 < [title length]) {
ch = [title characterAtIndex:i+1];
if (!mnemonic && ch != '&')
mnemonic = ch;
[result appendFormat:@"%C", ch];
i += 2;
} else
++i; // discard trailing &
}
if (button) {
[button setTitle:result];
if (mnemonic) {
[button setKeyEquivalent:[NSString stringWithCharacters:&mnemonic
length:1]];
[button
setKeyEquivalentModifierMask:NSAlternateKeyMask|NSCommandKeyMask];
}
}
return result;
}
// --------------------------------------------------------------------
class PDialog : public Dialog {
public:
PDialog(lua_State *L0, WINID parent, const char *caption);
virtual ~PDialog();
void itemAction(int idx);
int numberOfRows(int idx);
NSString *row(int idx, int row);
virtual void setMapped(lua_State *L, int idx);
virtual bool buildAndRun(int w, int h);
virtual void retrieveValues();
virtual void enableItem(int idx, bool value);
virtual void acceptDialog(lua_State *L);
private:
void fillComboBox(NSPopUpButton *cb, int idx);
void setTextView(NSTextView *tv, const std::string &s);
std::string getTextView(NSTextView *tv);
void layoutControls();
private:
NSPanel *iPanel;
IpeDialogDelegate *iDelegate;
NSMutableArray *iViews;
};
// --------------------------------------------------------------------
@implementation IpeDialogDelegate
- (void) ipeControl:(id) sender
{
self.dialog->itemAction([sender tag]);
}
- (BOOL) windowShouldClose:(id) sender
{
[NSApp stopModalWithCode:0];
return YES;
}
- (void) tableViewSelectionDidChange:(NSNotification *) notification
{
self.dialog->itemAction([[notification object] tag]);
}
- (NSInteger) numberOfRowsInTableView:(NSTableView *) tv
{
return self.dialog->numberOfRows([tv tag]);
}
- (id) tableView:(NSTableView *) tv
objectValueForTableColumn:(NSTableColumn *) col
row:(NSInteger)row
{
return self.dialog->row([tv tag], row);
}
- (NSView *) tableView:(NSTableView *) tv
viewForTableColumn:(NSTableColumn *) col
row:(NSInteger) row
{
NSTextField *result = [tv makeViewWithIdentifier:@"DialogList" owner:self];
if (result == nil) {
result = [[NSTextField alloc] initWithFrame:NSMakeRect(0., 0., 200., 20.)];
result.identifier = @"DialogList";
result.editable = NO;
result.bordered = NO;
result.drawsBackground = NO;
}
[result setStringValue:self.dialog->row([tv tag], row)];
return result;
}
@end
// --------------------------------------------------------------------
PDialog::PDialog(lua_State *L0, WINID parent, const char *caption)
: Dialog(L0, parent, caption)
{
//
}
PDialog::~PDialog()
{
//
}
void PDialog::acceptDialog(lua_State *L)
{
int accept = lua_toboolean(L, 2);
[NSApp stopModalWithCode:accept];
[iPanel close];
}
void PDialog::itemAction(int idx)
{
SElement &m = iElements[idx];
if (m.flags & EAccept) {
[NSApp stopModalWithCode:true];
[iPanel close];
} else if (m.flags & EReject) {
[NSApp stopModalWithCode:false];
[iPanel close];
} else if (m.lua_method != LUA_NOREF)
callLua(m.lua_method);
}
int PDialog::numberOfRows(int idx)
{
return iElements[idx].items.size();
}
NSString *PDialog::row(int idx, int row)
{
return S2N(iElements[idx].items[row]);
}
void PDialog::setMapped(lua_State *L, int idx)
{
SElement &m = iElements[idx];
NSView *ctrl = iViews[idx];
switch (m.type) {
case ELabel:
case EInput:
[((NSTextField *) ctrl) setStringValue:S2N(m.text)];
break;
case ETextEdit:
setTextView((NSTextView *) ctrl, m.text);
break;
case ECheckBox:
[((NSButton *) ctrl) setState:m.value];
break;
case EList:
// listbox gets items directly from items array
[((NSTableView *) ctrl) reloadData];
[((NSTableView *) ctrl) selectRowIndexes:[NSIndexSet
indexSetWithIndex:m.value]
byExtendingSelection:NO];
break;
case ECombo:
{
NSPopUpButton *b = (NSPopUpButton *) ctrl;
if (lua_istable(L, 3))
fillComboBox(b, idx);
[b selectItemAtIndex:m.value];
}
break;
default:
break; // EButton
}
}
void PDialog::retrieveValues()
{
for (int i = 0; i < int(iElements.size()); ++i) {
SElement &m = iElements[i];
NSView *ctrl = iViews[i];
switch (m.type) {
case EInput:
m.text = N2C([((NSTextField *) ctrl) stringValue]);
break;
case ETextEdit:
m.text = getTextView((NSTextView *) ctrl);
break;
case EList:
m.value = [((NSTableView *) ctrl) selectedRow];
break;
case ECombo:
m.value = [((NSPopUpButton *) ctrl) indexOfSelectedItem];
break;
case ECheckBox:
m.value = [((NSButton *) ctrl) intValue];
break;
default:
break; // label and button - nothing to do
}
}
}
void PDialog::enableItem(int idx, bool value)
{
if (iElements[idx].type != ETextEdit)
[((NSControl *) iViews[idx]) setEnabled:value];
}
void PDialog::fillComboBox(NSPopUpButton *cb, int idx)
{
SElement &m = iElements[idx];
[cb removeAllItems];
for (int k = 0; k < int(m.items.size()); ++k)
[cb addItemWithTitle:S2N(m.items[k])];
}
void PDialog::setTextView(NSTextView *tv, const std::string &s)
{
NSAttributedString *n = [[NSAttributedString alloc] initWithString:S2N(s)];
[[tv textStorage] setAttributedString:n];
tv.textColor = [NSColor textColor];
}
std::string PDialog::getTextView(NSTextView *tv)
{
return std::string(N2C([[tv textStorage] string]));
}
// --------------------------------------------------------------------
void PDialog::layoutControls()
{
double gap = 12.0;
int buttonGap = 12.0;
NSView *content = [iPanel contentView];
// create row guides
NSMutableArray *rows = [NSMutableArray arrayWithCapacity:iNoRows];
for (int i = 0; i < iNoRows; ++i) {
id g = layoutGuide(content);
layout(content, g, "l=l");
layout(g, content, "r=r");
if (i > 0)
layout(g, rows[i-1], "t=b", gap);
[rows addObject:g];
}
layout(rows[0], content, "t=t", gap);
// create column guides
NSMutableArray *cols = [NSMutableArray arrayWithCapacity:iNoCols];
for (int i = 0; i < iNoCols; ++i) {
id g = layoutGuide(content);
layout(content, g, "t=t");
layout(g, rows[iNoRows-1], "b=b");
if (i > 0)
layout(g, cols[i-1], "l=r", gap);
[cols addObject:g];
}
layout(cols[0], content, "l=l", gap);
layout(content, cols[iNoCols-1], "r=r", gap);
// layout buttons
NSView *lastButton = nil;
int bcount = 0;
for (size_t i = 0; i < iElements.size(); ++i) {
SElement &m = iElements[i];
if (m.row >= 0)
continue;
NSView *w = iViews[i];
++bcount;
layout(w, rows[iNoRows-1], "t=b", buttonGap);
layout(content, w, "b=b", buttonGap);
if (lastButton) {
if (bcount == 3)
layout(lastButton, w, "l>r", buttonGap);
else
layout(lastButton, w, "l=r", buttonGap);
layout(w, lastButton, "w=w");
} else
layout(content, w, "r=r", buttonGap);
lastButton = w;
}
if (bcount > 2)
layout(lastButton, content, "l=l", buttonGap);
else if (bcount)
layout(lastButton, content, "l>l", buttonGap);
else
// no button added using "addButton". Probably an old ipelet.
layout(content, rows[iNoRows-1], "b=b", gap);
while (int(iColStretch.size()) < iNoCols)
iColStretch.push_back(0);
while (int(iRowStretch.size()) < iNoRows)
iRowStretch.push_back(0);
// layout rows and columns
for (size_t i = 0; i < iElements.size(); ++i) {
SElement &m = iElements[i];
if (m.row < 0)
continue;
NSView *w = iViews[i];
if (m.type == EList || m.type == ETextEdit)
w = [[w superview] superview];
layout(w, rows[m.row], "t=t");
if (m.type == ECombo || m.type == ECheckBox)
layout(rows[m.row + m.rowspan - 1], w, "b>b");
else
layout(rows[m.row + m.rowspan - 1], w, "b=b");
layout(w, cols[m.col], "l=l");
layout(w, cols[m.col + m.colspan - 1], "r=r");
if (m.type == EInput || m.type == ETextEdit)
layout(w, nil, "w>0", 100);
// does it have stretch?
BOOL rowStretch = NO;
for (int r = m.row; r < m.row + m.rowspan; ++r)
if (iRowStretch[r] > 0)
rowStretch = YES;
BOOL colStretch = NO;
for (int c = m.col; c < m.col + m.colspan; ++c)
if (iColStretch[c] > 0)
colStretch = YES;
NSLayoutPriority rowpri = rowStretch ? 250.0 : 750.0;
NSLayoutPriority colpri = colStretch ? 250.0 : 550.0;
[w setContentHuggingPriority:rowpri
forOrientation:NSLayoutConstraintOrientationVertical];
[w setContentHuggingPriority:colpri
forOrientation:NSLayoutConstraintOrientationHorizontal];
}
// make columns with stretch 1 equally wide
NSView *g1 = nil;
for (int i = 0; i < iNoCols; ++i) {
if (iColStretch[i] == 1) {
if (g1 == nil)
g1 = cols[i];
else
layout(g1, cols[i], "w=w");
}
}
}
// --------------------------------------------------------------------
bool PDialog::buildAndRun(int w, int h)
{
NSUInteger style = NSTitledWindowMask|NSResizableWindowMask;
if (!iIgnoreEscape)
style |= NSClosableWindowMask;
iPanel = [[NSPanel alloc]
initWithContentRect:NSMakeRect(400.,800., w, h)
styleMask:style
backing:NSBackingStoreBuffered
defer:YES];
[iPanel setTitle:S2N(iCaption)];
hDialog = iPanel;
iDelegate = [[IpeDialogDelegate alloc] init];
[iDelegate setDialog:this];
[iPanel setDelegate:iDelegate];
iViews = [NSMutableArray arrayWithCapacity:iElements.size()];
NSView *content = [iPanel contentView];
NSView *focusCtrl = nil;
for (int i = 0; i < int(iElements.size()); ++i) {
SElement &m = iElements[i];
NSControl *ctrl = nil;
NSView *view = nil;
NSScrollView *scroll = nil;
if (m.row < 0) {
NSButton *b = [[NSButton alloc] initWithFrame:NSZeroRect];
[b setButtonType:NSMomentaryPushInButton];
ipeui_set_mnemonic(S2N(m.text), b);
[b setImagePosition:NSNoImage];
[b setBezelStyle:NSRoundedBezelStyle];
ctrl = b;
if (m.flags & EAccept) {
[b setKeyEquivalent:@"\r"];
[b setKeyEquivalentModifierMask:NSCommandKeyMask];
}
} else {
switch (m.type) {
case ELabel:
{
NSTextField *t = [[NSTextField alloc] initWithFrame:NSZeroRect];
t.stringValue= S2N(m.text);
t.bordered= NO;
t.drawsBackground = NO;
t.editable = NO;
ctrl = t;
}
break;
case EButton:
{
NSButton *b = [[NSButton alloc] initWithFrame:NSZeroRect];
[b setButtonType:NSMomentaryPushInButton];
ipeui_set_mnemonic(S2N(m.text), b);
[b setImagePosition:NSNoImage];
[b setBezelStyle:NSRoundedBezelStyle];
ctrl = b;
}
break;
case ECheckBox:
{
NSButton *b = [[NSButton alloc] initWithFrame:NSZeroRect];
[b setButtonType:NSSwitchButton];
ipeui_set_mnemonic(S2N(m.text), b);
[b setState:m.value ? NSOnState : NSOffState];
ctrl = b;
}
break;
case EInput:
{
NSTextField *t = [[NSTextField alloc] initWithFrame:NSZeroRect];
[t setStringValue:S2N(m.text)];
if (m.flags & ESelectAll)
[t selectText:content];
ctrl = t;
}
break;
case ETextEdit:
{
scroll = [[NSScrollView alloc] initWithFrame:NSZeroRect];
NSTextView *tv = [[NSTextView alloc] initWithFrame:NSZeroRect];
tv.editable = !(m.flags & (EReadOnly|EDisabled));
tv.richText = NO;
tv.allowsUndo = YES;
tv.continuousSpellCheckingEnabled = !!(m.flags & ESpellCheck);
tv.automaticSpellingCorrectionEnabled = NO;
tv.automaticQuoteSubstitutionEnabled = NO;
tv.automaticTextReplacementEnabled = NO;
tv.automaticDataDetectionEnabled = NO;
tv.automaticDashSubstitutionEnabled = NO;
setTextView(tv, m.text);
view = tv;
}
break;
case ECombo:
{
NSPopUpButton *b = [[NSPopUpButton alloc] initWithFrame:NSZeroRect
pullsDown:NO];
fillComboBox(b, i);
[b selectItemAtIndex:m.value];
ctrl = b;
}
break;
case EList:
{
scroll = [[NSScrollView alloc] initWithFrame:NSZeroRect];
NSTableView *tv = [[NSTableView alloc] initWithFrame:NSZeroRect];
NSTableColumn *column = [[NSTableColumn alloc]
initWithIdentifier:@"col1"];
[tv addTableColumn:column];
[tv setTag:i]; // needed before adding data source
[tv setDataSource:iDelegate];
[tv setHeaderView:nil];
[tv selectRowIndexes:[NSIndexSet indexSetWithIndex:m.value]
byExtendingSelection:NO];
[tv setDelegate:iDelegate];
ctrl = tv;
}
break;
default:
break;
}
}
if (ctrl) {
ctrl.enabled = !(m.flags & EDisabled);
ctrl.tag = i;
if (m.type != EList) {
ctrl.action = @selector(ipeControl:);
ctrl.target = iDelegate;
}
view = ctrl;
}
if (m.flags & EFocused)
focusCtrl = view;
if (scroll) {
[view setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
[scroll setDocumentView:view];
scroll.hasVerticalScroller = YES;
layout(scroll, nil, "h>0", 100.0);
layout(scroll, nil, "w>0", 160.0);
}
[view setContentCompressionResistancePriority:NSLayoutPriorityRequired
forOrientation:NSLayoutConstraintOrientationVertical];
[iViews addObject:view];
addToLayout(content, scroll ? scroll : view);
}
layoutControls();
// set keyboard focus
if (focusCtrl)
[iPanel makeFirstResponder:focusCtrl];
// this is such a hack, but it seems to work for Ipe
NSMenu *editMenu = [[[NSApp mainMenu] itemAtIndex:2] submenu];
NSMenuItem *undoItem = [editMenu itemAtIndex:0];
NSMenuItem *redoItem = [editMenu itemAtIndex:1];
undoItem.action = @selector(undo:);
redoItem.action = @selector(redo:);
int result = [NSApp runModalForWindow:iPanel];
retrieveValues(); // for future reference
undoItem.action = @selector(ipeMenuAction:);
redoItem.action = @selector(ipeMenuAction:);
iPanel = nil;
return result;
}
// --------------------------------------------------------------------
static int dialog_constructor(lua_State *L)
{
WINID parent = check_winid(L, 1);
const char *s = luaL_checkstring(L, 2);
Dialog **dlg = (Dialog **) lua_newuserdata(L, sizeof(Dialog *));
*dlg = nullptr;
luaL_getmetatable(L, "Ipe.dialog");
lua_setmetatable(L, -2);
*dlg = new PDialog(L, parent, s);
return 1;
}
// --------------------------------------------------------------------
void editor_thread(const char *cmd)
{
int result = std::system(cmd);
(void) result;
[NSApp abortModal];
}
static int ipeui_wait(lua_State *L)
{
const char *cmd = luaL_checkstring(L, 2);
NSPanel *panel = [[NSPanel alloc]
initWithContentRect:NSMakeRect(400.,800., 200, 100)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:YES];
panel.title = @"Ipe: waiting";
NSTextField *l = [[NSTextField alloc]
initWithFrame:NSMakeRect(0., 0., 200., 100.)];
l.stringValue = @"Waiting for external editor";
l.editable = NO;
l.bordered = NO;
l.drawsBackground = NO;
panel.contentView = l;
std::thread t(editor_thread, cmd);
[NSApp runModalForWindow:panel];
[panel close];
t.join();
return 0;
}
// --------------------------------------------------------------------
class PMenu;
@interface IpePopupMenuItem : NSMenuItem
@property NSString *ipeName; // name of single item
@property int ipeSubmenuIndex; // index of item in submenu
@property NSString *ipeSubmenuName; // name of item in submenu
@property PMenu *ipeMenu;
- (void) ipePopupAction:(id) sender;
@end
class PMenu : public Menu {
public:
PMenu();
virtual int add(lua_State *L);
virtual int execute(lua_State *L);
void itemSelected(IpePopupMenuItem *item);
private:
NSMenu *iMenu;
IpePopupMenuItem __unsafe_unretained *iSelected;
};
// --------------------------------------------------------------------
@implementation IpePopupMenuItem
- (void) ipePopupAction:(id) sender
{
IpePopupMenuItem *item = (IpePopupMenuItem *) sender;
self.ipeMenu->itemSelected(item);
}
@end
// --------------------------------------------------------------------
PMenu::PMenu()
{
iMenu = [[NSMenu alloc] init];
}
void PMenu::itemSelected(IpePopupMenuItem *item)
{
iSelected = item;
}
int PMenu::execute(lua_State *L)
{
NSPoint p = { luaL_checknumber(L, 2), luaL_checknumber(L, 3) };
iSelected = nil;
BOOL result = [iMenu popUpMenuPositioningItem:nil
atLocation:p
inView:nil];
if (result && iSelected) {
lua_pushstring(L, N2C(iSelected.ipeName));
lua_pushinteger(L, iSelected.ipeSubmenuIndex);
if (iSelected.ipeSubmenuName)
lua_pushstring(L, N2C(iSelected.ipeSubmenuName));
else
lua_pushliteral(L, "");
return 3;
} else
return 0;
}
NSImage *colorIcon(double red, double green, double blue, int pixels)
{
NSImage *icon = [NSImage imageWithSize:NSMakeSize(pixels, pixels)
flipped:NO
drawingHandler:^(NSRect rect) {
CGContextRef myContext = [[NSGraphicsContext currentContext] CGContext];
CGContextSetRGBFillColor(myContext, red, green, blue, 1.0);
CGContextFillRect(myContext, CGRectMake(0, 0, pixels, pixels));
return YES;
}];
return icon;
}
int PMenu::add(lua_State *L)
{
const char *name = luaL_checkstring(L, 2);
const char *title = luaL_checkstring(L, 3);
if (lua_gettop(L) == 3) {
IpePopupMenuItem *item = [[IpePopupMenuItem alloc]
initWithTitle:ipeui_set_mnemonic(C2N(title),nil)
action:@selector(ipePopupAction:)
keyEquivalent:@""];
[item setIpeName:C2N(name)];
[item setIpeSubmenuIndex:0];
[item setTarget:item];
[item setIpeMenu:this];
[iMenu addItem:item];
} else {
luaL_argcheck(L, lua_istable(L, 4), 4, "argument is not a table");
bool hasmap = !lua_isnoneornil(L, 5) && lua_isfunction(L, 5);
bool hastable = !hasmap && !lua_isnoneornil(L, 5);
bool hascolor = !lua_isnoneornil(L, 6) && lua_isfunction(L, 6);
bool hascheck = !hascolor && !lua_isnoneornil(L, 6);
if (hastable)
luaL_argcheck(L, lua_istable(L, 5), 5,
"argument is not a function or table");
const char *current = nullptr;
if (hascheck) {
luaL_argcheck(L, lua_isstring(L, 6), 6,
"argument is not a function or string");
current = luaL_checkstring(L, 6);
}
NSMenu *sm = [[NSMenu alloc] initWithTitle:C2N(title)];
if (hascolor)
[sm setShowsStateColumn:NO];
int no = lua_rawlen(L, 4);
for (int i = 1; i <= no; ++i) {
lua_rawgeti(L, 4, i);
luaL_argcheck(L, lua_isstring(L, -1), 4, "items must be strings");
const char *label = lua_tostring(L, -1);
if (hastable) {
lua_rawgeti(L, 5, i);
luaL_argcheck(L, lua_isstring(L, -1), 5, "labels must be strings");
} else if (hasmap) {
lua_pushvalue(L, 5); // function
lua_pushnumber(L, i); // index
lua_pushvalue(L, -3); // name
lua_call(L, 2, 1); // function returns label
luaL_argcheck(L, lua_isstring(L, -1), 5,
"function does not return string");
} else
lua_pushvalue(L, -1);
const char *text = lua_tostring(L, -1);
IpePopupMenuItem *item = [[IpePopupMenuItem alloc]
initWithTitle:ipeui_set_mnemonic(C2N(text),nil)
action:@selector(ipePopupAction:)
keyEquivalent:@""];
[item setIpeName:C2N(name)];
[item setIpeSubmenuIndex:i];
[item setIpeSubmenuName:C2N(label)];
[item setTarget:item];
[item setIpeMenu:this];
if (hascheck && !strcmp(label, current))
[item setState:NSOnState];
[sm addItem:item];
if (hascolor) {
lua_pushvalue(L, 6); // function
lua_pushnumber(L, i); // index
lua_pushvalue(L, -4); // name
lua_call(L, 2, 3); // function returns red, green, blue
double red = luaL_checknumber(L, -3);
double green = luaL_checknumber(L, -2);
double blue = luaL_checknumber(L, -1);
lua_pop(L, 3); // pop result
NSImage *im = colorIcon(red, green, blue, COLORICONSIZE);
[item setImage:im];
}
lua_pop(L, 2); // item, text
}
NSMenuItem *mitem = [[NSMenuItem alloc]
initWithTitle:ipeui_set_mnemonic(C2N(title),nil)
action:nullptr
keyEquivalent:@""];
[mitem setSubmenu:sm];
[iMenu addItem:mitem];
}
return 0;
}
// --------------------------------------------------------------------
static int menu_constructor(lua_State *L)
{
// check_winid(L, 1);
Menu **m = (Menu **) lua_newuserdata(L, sizeof(Menu *));
*m = nullptr;
luaL_getmetatable(L, "Ipe.menu");
lua_setmetatable(L, -2);
*m = new PMenu();
return 1;
}
// --------------------------------------------------------------------
class PTimer;
@interface IpeTimerDelegate : NSObject
@property PTimer *ptimer;
- (void) fired:(NSTimer *) timer;
@end
class PTimer : public Timer {
public:
PTimer(lua_State *L0, int lua_object, const char *method);
virtual int setInterval(lua_State *L);
virtual int active(lua_State *L);
virtual int start(lua_State *L);
virtual int stop(lua_State *L);
void fired();
private:
int iInterval;
NSTimer *iTimer;
IpeTimerDelegate *iDelegate;
};
// --------------------------------------------------------------------
@implementation IpeTimerDelegate
- (void) fired:(NSTimer *) timer
{
self.ptimer->fired();
}
@end
PTimer::PTimer(lua_State *L0, int lua_object, const char *method)
: Timer(L0, lua_object, method)
{
iTimer = nil;
iInterval = 0;
iDelegate = [[IpeTimerDelegate alloc] init];
iDelegate.ptimer = this;
}
void PTimer::fired()
{
callLua();
}
int PTimer::setInterval(lua_State *L)
{
iInterval = int(luaL_checkinteger(L, 2));
return 0;
}
int PTimer::active(lua_State *L)
{
lua_pushboolean(L, iTimer && [iTimer isValid]);
return 1;
}
int PTimer::start(lua_State *L)
{
if (iTimer)
luaL_argerror(L, 1, "timer is already started");
iTimer = [NSTimer scheduledTimerWithTimeInterval:iInterval / 1000.0
target:iDelegate
selector:@selector(fired:)
userInfo:nil
repeats:!iSingleShot];
return 0;
}
int PTimer::stop(lua_State *L)
{
if (iTimer)
[iTimer invalidate];
iTimer = nil;
return 0;
}
// --------------------------------------------------------------------
static int timer_constructor(lua_State *L)
{
luaL_argcheck(L, lua_istable(L, 1), 1, "argument is not a table");
const char *method = luaL_checkstring(L, 2);
Timer **t = (Timer **) lua_newuserdata(L, sizeof(Timer *));
*t = nullptr;
luaL_getmetatable(L, "Ipe.timer");
lua_setmetatable(L, -2);
// create a table with weak reference to Lua object
lua_createtable(L, 1, 1);
lua_pushliteral(L, "v");
lua_setfield(L, -2, "__mode");
lua_pushvalue(L, -1);
lua_setmetatable(L, -2);
lua_pushvalue(L, 1);
lua_rawseti(L, -2, 1);
int lua_object = luaL_ref(L, LUA_REGISTRYINDEX);
*t = new PTimer(L, lua_object, method);
return 1;
}
// --------------------------------------------------------------------
static NSArray *make_filters(const char *s)
{
NSMutableArray *exts = [NSMutableArray arrayWithCapacity:10];
const char *p = s;
while (*p) {
p += 2; // skip *.
const char *q = p;
while (*q && *q != ';')
++q;
NSString *ext = [[NSString alloc] initWithBytes:p
length:q-p
encoding:NSUTF8StringEncoding];
[exts addObject:ext];
if (*q == ';')
++q;
p = q;
}
return exts;
}
@interface IpeFileDialogHelper : NSObject
@property (weak) NSSavePanel *panel;
@property NSPopUpButton *fileType;
@property NSMutableArray *filters;
- (instancetype) initFor:(NSSavePanel *) panel;
- (void) setupWithLua:(lua_State *) L;
- (void) changeFileType:(id) sender;
@end
@implementation IpeFileDialogHelper
- (instancetype) initFor:(NSSavePanel *) panel
{
self = [super init];
if (self) {
_panel = panel;
}
return self;
}
- (void) changeFileType:(id) sender
{
[self setFilter:[self.fileType indexOfSelectedItem]];
}
- (void) setupWithLua:(lua_State *) L
{
self.panel.message = C2N(luaL_checkstring(L, 3));
if (!lua_isnoneornil(L, 5)) {
const char *dir = luaL_checkstring(L, 5);
self.panel.directoryURL = [NSURL fileURLWithPath:C2N(dir) isDirectory:YES];
}
if (!lua_isnoneornil(L, 6)) {
auto url = [NSURL fileURLWithPath:C2N(luaL_checkstring(L, 6))];
self.panel.nameFieldStringValue = [url lastPathComponent];
}
self.panel.canCreateDirectories = YES;
self.panel.showsTagField = NO;
self.panel.canSelectHiddenExtension = YES;
self.fileType = [[NSPopUpButton alloc]
initWithFrame:NSMakeRect(0., 0., 200.0, 40.0)
pullsDown:NO];
self.fileType.target = self;
self.fileType.action = @selector(changeFileType:);
self.panel.accessoryView = self.fileType;
if (!lua_istable(L, 4))
luaL_argerror(L, 4, "table expected for filters");
int nFilters = lua_rawlen(L, 4);
self.filters = [NSMutableArray arrayWithCapacity:nFilters/2];
for (int i = 1; i <= nFilters; ++i) {
lua_rawgeti(L, 4, i);
luaL_argcheck(L, lua_isstring(L, -1), 4, "filter entry is not a string");
const char *s = lua_tostring(L, -1);
if (i % 2 == 1)
[self.fileType addItemWithTitle:C2N(s)];
else
[self.filters addObject:make_filters(s)];
lua_pop(L, 1); // element i
}
int selected = 0;
if (!lua_isnoneornil(L, 7))
selected = luaL_checkinteger(L, 7) - 1;
[self.fileType selectItemAtIndex:selected];
[self setFilter:selected];
}
- (void) setFilter:(int) filterIndex
{
if ([self.filters[filterIndex][0] isEqualToString:@"*"])
self.panel.allowedFileTypes = nil;
else
self.panel.allowedFileTypes = self.filters[filterIndex];
}
@end
static int ipeui_fileDialog(lua_State *L)
{
static const char * const typenames[] = { "open", "save", nullptr };
// not needed: NSWindow *win = check_winid(L, 1);
int type = luaL_checkoption(L, 2, nullptr, typenames);
if (type == 0) { // open file
NSOpenPanel *panel = [NSOpenPanel openPanel];
IpeFileDialogHelper *helper = [[IpeFileDialogHelper alloc] initFor:panel];
[helper setupWithLua:L];
if ([panel runModal] == NSFileHandlingPanelOKButton) {
lua_pushstring(L, N2C([[panel URLs][0] path]));
lua_pushinteger(L, [helper.fileType indexOfSelectedItem] + 1);
return 2;
}
} else { // save file
NSSavePanel *panel = [NSSavePanel savePanel];
IpeFileDialogHelper *helper = [[IpeFileDialogHelper alloc] initFor:panel];
[helper setupWithLua:L];
if ([panel runModal] == NSFileHandlingPanelOKButton) {
lua_pushstring(L, N2C([[panel URL] path]));
lua_pushinteger(L, [helper.fileType indexOfSelectedItem] + 1);
return 2;
}
}
return 0;
}
// --------------------------------------------------------------------
static int ipeui_getColor(lua_State *L)
{
NSWindow *win = check_winid(L, 1);
NSColorPanel *panel = [NSColorPanel sharedColorPanel];
if ([panel isVisible]) {
NSColor *rgb = [panel color];
lua_pushnumber(L, rgb.redComponent);
lua_pushnumber(L, rgb.greenComponent);
lua_pushnumber(L, rgb.blueComponent);
return 3;
} else {
const char *title = luaL_checkstring(L, 2);
double r = luaL_checknumber(L, 3);
double g = luaL_checknumber(L, 4);
double b = luaL_checknumber(L, 5);
NSColor *rgb = [NSColor colorWithRed:r green:g blue:b alpha:1.0];
[panel setColor:rgb];
[panel setTitle:C2N(title)];
[panel orderFront:win];
return 0;
}
}
// --------------------------------------------------------------------
static int ipeui_messageBox(lua_State *L)
{
static const char * const options[] = {
"none", "warning", "information", "question", "critical", nullptr };
static const char * const buttontype[] = {
"ok", "okcancel", "yesnocancel", "discardcancel",
"savediscardcancel", nullptr };
NSWindow *win = check_winid(L, 1);
int type = luaL_checkoption(L, 2, "none", options);
const char *text = luaL_checkstring(L, 3);
const char *details = nullptr;
if (!lua_isnoneornil(L, 4))
details = luaL_checkstring(L, 4);
int buttons = 0;
if (lua_isnumber(L, 5))
buttons = (int)luaL_checkinteger(L, 5);
else if (!lua_isnoneornil(L, 5))
buttons = luaL_checkoption(L, 5, nullptr, buttontype);
(void) win;
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:C2N(text)];
if (details)
[alert setInformativeText:C2N(details)];
NSAlertStyle astyle = NSInformationalAlertStyle;
switch (type) {
case 0:
default:
break;
case 1:
astyle = NSWarningAlertStyle;
break;
case 2:
break;
case 3:
// Question doesn't seem to exist on Cocoa
break;
case 4:
astyle = NSCriticalAlertStyle;
break;
}
[alert setAlertStyle:astyle];
switch (buttons) {
case 0:
default:
break;
case 1:
[alert addButtonWithTitle:@"Ok"];
[alert addButtonWithTitle:@"Cancel"];
break;
case 2:
[alert addButtonWithTitle:@"Yes"];
[alert addButtonWithTitle:@"No"];
[alert addButtonWithTitle:@"Cancel"];
break;
case 3:
[alert addButtonWithTitle:@"Discard"];
[alert addButtonWithTitle:@"Cancel"];
break;
case 4:
[alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Discard"];
[alert addButtonWithTitle:@"Cancel"];
break;
}
switch ([alert runModal]) {
case NSAlertFirstButtonReturn:
lua_pushnumber(L, 1);
break;
case NSAlertSecondButtonReturn:
if (buttons == 2 || buttons == 4)
lua_pushnumber(L, 0);
else
lua_pushnumber(L, -1);
break;
case NSAlertThirdButtonReturn:
default:
lua_pushnumber(L, -1);
break;
}
return 1;
}
// --------------------------------------------------------------------
static int ipeui_currentDateTime(lua_State *L)
{
NSDate *now = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
unsigned unitFlags =
NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay |
NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *st = [gregorian components:unitFlags fromDate:now];
char buf[16];
sprintf(buf, "%04ld%02ld%02ld%02ld%02ld%02ld",
long(st.year), long(st.month), long(st.day),
long(st.hour), long(st.minute), long(st.second));
lua_pushstring(L, buf);
return 1;
}
static int ipeui_startBrowser(lua_State *L)
{
const char *url = luaL_checkstring(L, 1);
int res = [[NSWorkspace sharedWorkspace]
openURL:[NSURL URLWithString:C2N(url)]];
lua_pushboolean(L, res);
return 1;
}
// --------------------------------------------------------------------
static const struct luaL_Reg ipeui_functions[] = {
{ "waitDialog", ipeui_wait },
{ "Dialog", dialog_constructor },
{ "Timer", timer_constructor },
{ "Menu", menu_constructor },
{ "fileDialog", ipeui_fileDialog },
{ "getColor", ipeui_getColor },
{ "messageBox", ipeui_messageBox },
{ "currentDateTime", ipeui_currentDateTime },
{ "startBrowser", ipeui_startBrowser },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
int luaopen_ipeui(lua_State *L)
{
luaL_newlib(L, ipeui_functions);
lua_setglobal(L, "ipeui");
luaopen_ipeui_common(L);
return 0;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipeui/ipeuilayout_cocoa.h 0000644 0001750 0001750 00000003200 13561570220 020017 0 ustar otfried otfried // -*- objc -*-
// ipeuilayout_cocoa.h
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef IPEUILAYOUT_H
#define IPEUILAYOUT_H
extern void addToLayout(NSView *view, NSView *subview);
extern id layoutGuide(NSView *owner);
extern void activateConstraint(NSLayoutConstraint *c, BOOL active);
extern NSLayoutConstraint *layout(id a, id b, const char *rel,
double gap = 0.0,
double multiplier=1.0,
BOOL activate=YES);
extern NSString *ipeui_set_mnemonic(NSString *title, NSButton *button);
// --------------------------------------------------------------------
#endif
ipe-7.2.13/src/ipeui/ipeui_qt.cpp 0000644 0001750 0001750 00000056660 13561570220 016476 0 ustar otfried otfried // --------------------------------------------------------------------
// Lua bindings for Qt dialogs
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipeui_qt.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// --------------------------------------------------------------------
inline void push_string(lua_State *L, const QString &str)
{
lua_pushstring(L, str.toUtf8());
}
inline QString toqstring(lua_State *L, int i)
{
return QString::fromUtf8(lua_tostring(L, i));
}
inline QString checkqstring(lua_State *L, int i)
{
return QString::fromUtf8(luaL_checkstring(L, i));
}
// --------------------------------------------------------------------
class XmlHighlighter : public QSyntaxHighlighter
{
public:
XmlHighlighter(QTextEdit *textEdit);
protected:
void applyFormat(const QString &text, QRegExp &exp,
const QTextCharFormat &format);
virtual void highlightBlock(const QString &text);
};
void XmlHighlighter::applyFormat(const QString &text, QRegExp &exp,
const QTextCharFormat &format)
{
int index = text.indexOf(exp);
while (index >= 0) {
int length = exp.matchedLength();
setFormat(index, length, format);
index = text.indexOf(exp, index + length);
}
}
void XmlHighlighter::highlightBlock(const QString &text)
{
QTextCharFormat tagFormat, stringFormat, numberFormat;
tagFormat.setFontWeight(QFont::Bold);
tagFormat.setForeground(Qt::blue);
stringFormat.setForeground(Qt::darkMagenta);
numberFormat.setForeground(Qt::red);
QRegExp tagExp( "<.*>" );
QRegExp stringExp( "\"[a-zA-Z]*\"" );
QRegExp numberExp( "[+|-]*[0-9]*.[0-9][0-9]*" );
applyFormat(text, tagExp, tagFormat);
applyFormat(text, stringExp, stringFormat);
applyFormat(text, numberExp, numberFormat);
}
XmlHighlighter::XmlHighlighter(QTextEdit *textEdit)
: QSyntaxHighlighter(textEdit)
{
// nothing
}
// --------------------------------------------------------------------
class LatexHighlighter : public QSyntaxHighlighter
{
public:
LatexHighlighter(QTextEdit *textEdit);
protected:
void applyFormat(const QString &text, QRegExp &exp,
const QTextCharFormat &format);
virtual void highlightBlock(const QString &text);
};
void LatexHighlighter::applyFormat(const QString &text, QRegExp &exp,
const QTextCharFormat &format)
{
int index = text.indexOf(exp);
while (index >= 0) {
int length = exp.matchedLength();
setFormat(index, length, format);
index = text.indexOf(exp, index + length);
}
}
void LatexHighlighter::highlightBlock(const QString &text)
{
QTextCharFormat mathFormat, tagFormat;
mathFormat.setForeground(Qt::red);
tagFormat.setFontWeight(QFont::Bold);
tagFormat.setForeground(Qt::blue);
QRegExp mathExp( "\\$[^$]+\\$" );
QRegExp tagExp( "\\\\[a-zA-Z]+" );
applyFormat(text, mathExp, mathFormat);
applyFormat(text, tagExp, tagFormat);
}
LatexHighlighter::LatexHighlighter(QTextEdit *textEdit)
: QSyntaxHighlighter(textEdit)
{
// nothing
}
// --------------------------------------------------------------------
PDialog::PDialog(lua_State *L0, WINID parent, const char *caption)
: QDialog(parent), Dialog(L0, parent, caption)
{
setWindowTitle(caption);
QVBoxLayout *vlo = new QVBoxLayout;
setLayout(vlo);
iGrid = new QGridLayout;
vlo->addLayout(iGrid);
iButtonArea = new QHBoxLayout;
vlo->addLayout(iButtonArea);
iButtonArea->addStretch(1);
QShortcut *shortcut = new QShortcut(QKeySequence("Ctrl+Return"), this);
connect(shortcut, &QShortcut::activated, this, &QDialog::accept);
}
PDialog::~PDialog()
{
//
}
void PDialog::keyPressEvent(QKeyEvent *e)
{
if (iIgnoreEscape && e->key() == Qt::Key_Escape)
return;
QDialog::keyPressEvent(e);
}
static void markupLog(QTextEdit *t, const QString &text)
{
QTextDocument *doc = new QTextDocument(t);
doc->setPlainText(text);
QTextCursor cursor(doc);
int curPos = 0;
int errNo = 0;
for (;;) {
int nextErr = text.indexOf(QLatin1String("\n!"), curPos);
if (nextErr < 0)
break;
int lines = 0;
while (curPos < nextErr + 1) {
if (text[curPos++] == QLatin1Char('\n'))
++lines;
}
cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, lines);
int pos = cursor.position();
cursor.movePosition(QTextCursor::Down);
cursor.setPosition(pos, QTextCursor::KeepAnchor);
++errNo;
QString s;
s.sprintf("err%d", errNo);
QTextCharFormat format;
format.setBackground(Qt::yellow);
format.setAnchorName(s);
format.setAnchor(true);
cursor.setCharFormat(format);
}
t->setDocument(doc);
t->scrollToAnchor(QLatin1String("err1"));
}
void PDialog::setMapped(lua_State *L, int idx)
{
SElement &m = iElements[idx];
QWidget *w = iWidgets[idx];
switch (m.type) {
case ELabel:
(qobject_cast(w))->setText(QString::fromUtf8(m.text.c_str()));
break;
case ECheckBox:
(qobject_cast(w))->setChecked(m.value);
break;
case ETextEdit:
(qobject_cast(w))->setText(QString::fromUtf8(m.text.c_str()));
break;
case EInput:
(qobject_cast(w))->setText(QString::fromUtf8(m.text.c_str()));
break;
case EList:
{
QListWidget *l = qobject_cast(w);
if (!lua_isnumber(L, 3)) {
l->clear();
for (int k = 0; k < int(m.items.size()); ++k)
l->addItem(QString::fromUtf8(m.items[k].c_str()));
}
l->setCurrentRow(m.value);
}
break;
case ECombo:
{
QComboBox *b = qobject_cast(w);
if (!lua_isnumber(L, 3)) {
b->clear();
for (int k = 0; k < int(m.items.size()); ++k)
b->addItem(QString::fromUtf8(m.items[k].c_str()));
}
b->setCurrentIndex(m.value);
}
break;
default:
break; // EButton
}
}
bool PDialog::buildAndRun(int w, int h)
{
for (int i = 0; i < int(iElements.size()); ++i) {
SElement &m = iElements[i];
if (m.row < 0) {
QPushButton *b = new QPushButton(QString::fromUtf8(m.text.c_str()), this);
iWidgets.push_back(b);
if (m.flags & EAccept) {
b->setDefault(true);
connect(b, &QPushButton::clicked, this, &QDialog::accept);
} else if (m.flags & EReject)
connect(b, &QPushButton::clicked, this, &QDialog::reject);
else if (m.lua_method != LUA_NOREF)
connect(b, &QPushButton::clicked, [&,method=m.lua_method](){ callLua(method); });
iButtonArea->addWidget(b);
} else {
QWidget *w = nullptr;
switch (m.type) {
case ELabel:
w = new QLabel(QString::fromUtf8(m.text.c_str()), this);
w->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
break;
case EButton:
{
QPushButton *b = new QPushButton(QString::fromUtf8(m.text.c_str()), this);
if (m.flags & EAccept)
connect(b, &QPushButton::clicked, this, &QDialog::accept);
else if (m.flags & EReject)
connect(b, &QPushButton::clicked, this, &QDialog::reject);
else if (m.lua_method != LUA_NOREF)
connect(b, &QPushButton::clicked, [&,method=m.lua_method](){ callLua(method); });
w = b;
}
break;
case ECheckBox:
{
QCheckBox *ch = new QCheckBox(QString::fromUtf8(m.text.c_str()), this);
ch->setChecked(m.value);
if (m.lua_method != LUA_NOREF)
connect(ch, &QCheckBox::stateChanged, [&,method=m.lua_method](int){ callLua(method); });
w = ch;
}
break;
case EInput:
{
QLineEdit *e = new QLineEdit(this);
e->setText(QString::fromUtf8(m.text.c_str()));
if (m.flags & ESelectAll)
e->selectAll();
w = e;
}
break;
case ETextEdit:
{
QTextEdit *t = new QTextEdit(this);
t->setAcceptRichText(false);
if (m.flags & EReadOnly)
t->setReadOnly(true);
if (m.flags & EXml)
(void) new XmlHighlighter(t);
else if (m.flags & ELatex)
(void) new LatexHighlighter(t);
QString text = QString::fromUtf8(m.text.c_str());
if (m.flags & ELogFile) {
markupLog(t, text);
} else
t->setPlainText(text);
if (m.flags & ESelectAll)
t->selectAll();
w = t;
}
break;
case ECombo:
{
QComboBox *b = new QComboBox(this);
for (int k = 0; k < int(m.items.size()); ++k)
b->addItem(QString::fromUtf8(m.items[k].c_str()));
b->setCurrentIndex(m.value);
if (m.lua_method != LUA_NOREF) {
connect(b, &QComboBox::activated,
[=](int index){ callLua(m.lua_method); });
}
w = b;
}
break;
case EList:
{
QListWidget *l = new QListWidget(this);
for (int k = 0; k < int(m.items.size()); ++k)
l->addItem(QString::fromUtf8(m.items[k].c_str()));
if (m.lua_method != LUA_NOREF) {
connect(l, &QListWidget::itemActivated,
[=](QListWidgetItem *){ callLua(m.lua_method); });
}
w = l;
}
break;
default:
break;
}
iWidgets.push_back(w);
gridlayout()->addWidget(w, m.row, m.col, m.rowspan, m.colspan);
if (m.flags & EFocused)
w->setFocus(Qt::OtherFocusReason);
if (m.flags & EDisabled)
w->setEnabled(false);
}
}
setMinimumSize(w, h);
int result = exec();
retrieveValues(); // for future reference
return (result == QDialog::Accepted);
}
void PDialog::retrieveValues()
{
for (int i = 0; i < int(iElements.size()); ++i) {
SElement &m = iElements[i];
QWidget *w = iWidgets[i];
switch (m.type) {
case EInput:
m.text = std::string((qobject_cast(w))->text().toUtf8());
break;
case ETextEdit:
m.text = std::string((qobject_cast(w))->
toPlainText().toUtf8());
break;
case EList:
{
int r = (qobject_cast(w))->currentRow();
m.value = (r >= 0) ? r : 0;
}
break;
case ECombo:
m.value = (qobject_cast(w))->currentIndex();
break;
case ECheckBox:
m.value = qobject_cast(w)->isChecked();
break;
default:
break; // label and button - nothing to do
}
}
}
void PDialog::enableItem(int idx, bool value)
{
iWidgets[idx]->setEnabled(value);
}
void PDialog::acceptDialog(lua_State *L)
{
int accept = lua_toboolean(L, 2);
QDialog::done(accept);
}
// --------------------------------------------------------------------
static int dialog_constructor(lua_State *L)
{
QWidget *parent = check_winid(L, 1);
const char *s = luaL_checkstring(L, 2);
Dialog **dlg = (Dialog **) lua_newuserdata(L, sizeof(Dialog *));
*dlg = nullptr;
luaL_getmetatable(L, "Ipe.dialog");
lua_setmetatable(L, -2);
*dlg = new PDialog(L, parent, s);
return 1;
}
// --------------------------------------------------------------------
MenuAction::MenuAction(const QString &name, int number,
const QString &item, const QString &text,
QWidget *parent)
: QAction(text, parent)
{
iName = name;
iItemName = item;
iNumber = number;
}
// --------------------------------------------------------------------
PMenu::PMenu(WINID parent)
{
iMenu = new QMenu();
}
PMenu::~PMenu()
{
delete iMenu;
}
int PMenu::execute(lua_State *L)
{
int vx = (int)luaL_checknumber(L, 2);
int vy = (int)luaL_checknumber(L, 3);
QAction *a = iMenu->exec(QPoint(vx, vy));
MenuAction *ma = qobject_cast(a);
if (ma) {
push_string(L, ma->name());
lua_pushnumber(L, ma->number());
push_string(L, ma->itemName());
return 3;
}
return 0;
}
#define SIZE 16
static QIcon colorIcon(double red, double green, double blue)
{
QPixmap pixmap(SIZE, SIZE);
pixmap.fill(QColor(int(red * 255.0 + 0.5),
int(green * 255.0 + 0.5),
int(blue * 255.0 + 0.5)));
return QIcon(pixmap);
}
int PMenu::add(lua_State *L)
{
QString name = checkqstring(L, 2);
QString title = checkqstring(L, 3);
if (lua_gettop(L) == 3) {
iMenu->addAction(new MenuAction(name, 0, QString(), title, iMenu));
} else {
luaL_argcheck(L, lua_istable(L, 4), 4, "argument is not a table");
bool hasmap = !lua_isnoneornil(L, 5) && lua_isfunction(L, 5);
bool hastable = !hasmap && !lua_isnoneornil(L, 5);
bool hascolor = !lua_isnoneornil(L, 6) && lua_isfunction(L, 6);
bool hascheck = !hascolor && !lua_isnoneornil(L, 6);
if (hastable)
luaL_argcheck(L, lua_istable(L, 5), 5,
"argument is not a function or table");
QString current;
if (hascheck) {
luaL_argcheck(L, lua_isstring(L, 6), 6,
"argument is not a function or string");
current = checkqstring(L, 6);
}
int no = lua_rawlen(L, 4);
QMenu *sm = new QMenu(title, iMenu);
for (int i = 1; i <= no; ++i) {
lua_rawgeti(L, 4, i);
luaL_argcheck(L, lua_isstring(L, -1), 4, "items must be strings");
QString item = toqstring(L, -1);
QString text = item;
if (hastable) {
lua_rawgeti(L, 5, i);
luaL_argcheck(L, lua_isstring(L, -1), 5, "labels must be strings");
text = toqstring(L, -1);
lua_pop(L, 1);
}
if (hasmap) {
lua_pushvalue(L, 5); // function
lua_pushnumber(L, i); // index
lua_pushvalue(L, -3); // name
lua_call(L, 2, 1); // function returns label
luaL_argcheck(L, lua_isstring(L, -1), 5,
"function does not return string");
text = toqstring(L, -1);
lua_pop(L, 1); // pop result
}
MenuAction *ma = new MenuAction(name, i, item, text, sm);
if (hascolor) {
lua_pushvalue(L, 6); // function
lua_pushnumber(L, i); // index
lua_pushvalue(L, -3); // name
lua_call(L, 2, 3); // function returns red, green, blue
double red = luaL_checknumber(L, -3);
double green = luaL_checknumber(L, -2);
double blue = luaL_checknumber(L, -1);
lua_pop(L, 3); // pop result
QIcon icon = colorIcon(red, green, blue);
ma->setIcon(icon);
ma->setIconVisibleInMenu(true);
}
if (hascheck) {
ma->setCheckable(true);
ma->setChecked(item == current);
}
lua_pop(L, 1); // item
sm->addAction(ma);
}
iMenu->addMenu(sm);
}
return 0;
}
static int menu_constructor(lua_State *L)
{
QWidget *parent = check_winid(L, 1);
Menu **m = (Menu **) lua_newuserdata(L, sizeof(PMenu *));
*m = nullptr;
luaL_getmetatable(L, "Ipe.menu");
lua_setmetatable(L, -2);
*m = new PMenu(parent);
return 1;
}
// --------------------------------------------------------------------
class EditorThread : public QThread
{
public:
EditorThread(const QString &cmd);
protected:
virtual void run();
private:
QString iCommand;
};
EditorThread::EditorThread(const QString &cmd) : QThread()
{
iCommand = cmd;
}
void EditorThread::run()
{
int result = std::system(iCommand.toUtf8());
(void) result;
}
class EditorDialog : public QDialog
{
public:
EditorDialog(QWidget *parent = nullptr);
protected:
void closeEvent(QCloseEvent *ev);
};
EditorDialog::EditorDialog(QWidget *parent)
: QDialog(parent)
{
QGridLayout *lo = new QGridLayout;
setLayout(lo);
setWindowTitle("Ipe: waiting");
QLabel *l = new QLabel("Waiting for external editor", this);
lo->addWidget(l, 0, 0);
}
void EditorDialog::closeEvent(QCloseEvent *ev)
{
ev->ignore();
}
static int ipeui_wait(lua_State *L)
{
QString cmd = checkqstring(L, 2);
EditorDialog *dialog = new EditorDialog();
EditorThread *thread = new EditorThread(cmd);
dialog->connect(thread, &QThread::finished, dialog, &QDialog::accept);
thread->start();
dialog->exec();
delete dialog;
delete thread;
return 0;
}
// --------------------------------------------------------------------
PTimer::PTimer(lua_State *L0, int lua_object, const char *method)
: Timer(L0, lua_object, method)
{
iTimer = new QTimer();
connect(iTimer, &QTimer::timeout, [&]() {
if (iSingleShot)
iTimer->stop();
callLua();
});
}
PTimer::~PTimer()
{
delete iTimer;
}
int PTimer::setInterval(lua_State *L)
{
int t = (int)luaL_checkinteger(L, 2);
iTimer->setInterval(t);
return 0;
}
int PTimer::active(lua_State *L)
{
lua_pushboolean(L, iTimer->isActive());
return 1;
}
int PTimer::start(lua_State *L)
{
iTimer->start();
return 0;
}
int PTimer::stop(lua_State *L)
{
iTimer->stop();
return 0;
}
// --------------------------------------------------------------------
static int timer_constructor(lua_State *L)
{
luaL_argcheck(L, lua_istable(L, 1), 1, "argument is not a table");
const char *method = luaL_checkstring(L, 2);
Timer **t = (Timer **) lua_newuserdata(L, sizeof(Timer *));
*t = nullptr;
luaL_getmetatable(L, "Ipe.timer");
lua_setmetatable(L, -2);
// create a table with weak reference to Lua object
lua_createtable(L, 1, 1);
lua_pushliteral(L, "v");
lua_setfield(L, -2, "__mode");
lua_pushvalue(L, -1);
lua_setmetatable(L, -2);
lua_pushvalue(L, 1);
lua_rawseti(L, -2, 1);
int lua_object = luaL_ref(L, LUA_REGISTRYINDEX);
*t = new PTimer(L, lua_object, method);
return 1;
}
// --------------------------------------------------------------------
static int ipeui_getColor(lua_State *L)
{
QWidget *parent = check_winid(L, 1);
QString title = checkqstring(L, 2);
QColor initial = QColor::fromRgbF(luaL_checknumber(L, 3),
luaL_checknumber(L, 4),
luaL_checknumber(L, 5));
#if QT_VERSION >= 0x040500
QColor changed = QColorDialog::getColor(initial, parent, title);
#else
QColor changed = QColorDialog::getColor(initial, parent);
#endif
if (changed.isValid()) {
lua_pushnumber(L, changed.redF());
lua_pushnumber(L, changed.greenF());
lua_pushnumber(L, changed.blueF());
return 3;
} else
return 0;
}
// --------------------------------------------------------------------
static int ipeui_fileDialog(lua_State *L)
{
static const char * const typenames[] = { "open", "save", nullptr };
QWidget *parent = check_winid(L, 1);
int type = luaL_checkoption(L, 2, nullptr, typenames);
QString caption = checkqstring(L, 3);
if (!lua_istable(L, 4))
luaL_argerror(L, 4, "table expected for filters");
QStringList filters;
int nFilters = lua_rawlen(L, 4);
for (int i = 1; i <= nFilters; i += 2) { // skip Windows versions
lua_rawgeti(L, 4, i);
luaL_argcheck(L, lua_isstring(L, -1), 4, "filter entry is not a string");
filters += checkqstring(L, -1);
lua_pop(L, 1); // element i
}
QString dir;
if (!lua_isnoneornil(L, 5))
dir = checkqstring(L, 5);
QString name;
if (!lua_isnoneornil(L, 6))
name = checkqstring(L, 6);
int selected = 0;
if (!lua_isnoneornil(L, 7))
selected = luaL_checkinteger(L, 7);
QFileDialog dialog(parent);
dialog.setWindowTitle(caption);
dialog.setNameFilters(filters);
dialog.setConfirmOverwrite(false);
if (type == 0) {
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setAcceptMode(QFileDialog::AcceptOpen);
} else {
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setAcceptMode(QFileDialog::AcceptSave);
}
if (selected)
dialog.selectNameFilter(filters[selected-1]);
if (!dir.isNull())
dialog.setDirectory(dir);
if (!name.isNull())
dialog.selectFile(name);
if (dialog.exec() == QDialog::Accepted) {
QStringList fns = dialog.selectedFiles();
if (!fns.isEmpty()) {
push_string(L, fns[0]);
QString f = dialog.selectedNameFilter();
int sf = 0;
while (sf < filters.size() && filters[sf] != f)
++sf;
lua_pushinteger(L, (sf < filters.size()) ? sf + 1 : 0);
return 2;
}
}
return 0;
}
// --------------------------------------------------------------------
static int ipeui_messageBox(lua_State *L)
{
static const char * const options[] = {
"none", "warning", "information", "question", "critical", nullptr };
static const char * const buttontype[] = {
"ok", "okcancel", "yesnocancel", "discardcancel",
"savediscardcancel", nullptr };
QWidget *parent = check_winid(L, 1);
int type = luaL_checkoption(L, 2, "none", options);
QString text = checkqstring(L, 3);
QString details;
if (!lua_isnoneornil(L, 4))
details = checkqstring(L, 4);
int buttons = 0;
if (lua_isnumber(L, 5))
buttons = luaL_checkinteger(L, 5);
else if (!lua_isnoneornil(L, 5))
buttons = luaL_checkoption(L, 5, nullptr, buttontype);
QMessageBox msgBox(parent);
msgBox.setText(text);
msgBox.setInformativeText(details);
switch (type) {
case 0:
default:
msgBox.setIcon(QMessageBox::NoIcon);
break;
case 1:
msgBox.setIcon(QMessageBox::Warning);
break;
case 2:
msgBox.setIcon(QMessageBox::Information);
break;
case 3:
msgBox.setIcon(QMessageBox::Question);
break;
case 4:
msgBox.setIcon(QMessageBox::Critical);
break;
}
switch (buttons) {
case 0:
default:
msgBox.setStandardButtons(QMessageBox::Ok);
break;
case 1:
msgBox.setStandardButtons(QMessageBox::Ok|QMessageBox::Cancel);
break;
case 2:
msgBox.setStandardButtons(QMessageBox::Yes|QMessageBox::No|
QMessageBox::Cancel);
break;
case 3:
msgBox.setStandardButtons(QMessageBox::Discard|QMessageBox::Cancel);
break;
case 4:
msgBox.setStandardButtons(QMessageBox::Save|QMessageBox::Discard|
QMessageBox::Cancel);
break;
}
int ret = msgBox.exec();
switch (ret) {
case QMessageBox::Ok:
case QMessageBox::Yes:
case QMessageBox::Save:
lua_pushnumber(L, 1);
break;
case QMessageBox::No:
case QMessageBox::Discard:
lua_pushnumber(L, 0);
break;
case QMessageBox::Cancel:
default:
lua_pushnumber(L, -1);
break;
}
return 1;
}
// --------------------------------------------------------------------
static int ipeui_currentDateTime(lua_State *L)
{
QDateTime dt = QDateTime::currentDateTime();
QString mod;
mod.sprintf("%04d%02d%02d%02d%02d%02d",
dt.date().year(), dt.date().month(), dt.date().day(),
dt.time().hour(), dt.time().minute(), dt.time().second());
push_string(L, mod);
return 1;
}
// --------------------------------------------------------------------
static const struct luaL_Reg ipeui_functions[] = {
{ "Dialog", dialog_constructor },
{ "Menu", menu_constructor },
{ "Timer", timer_constructor },
{ "getColor", ipeui_getColor },
{ "fileDialog", ipeui_fileDialog },
{ "messageBox", ipeui_messageBox },
{ "waitDialog", ipeui_wait },
{ "currentDateTime", ipeui_currentDateTime },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
int luaopen_ipeui(lua_State *L)
{
luaL_newlib(L, ipeui_functions);
lua_setglobal(L, "ipeui");
luaopen_ipeui_common(L);
return 0;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipeui/ipeui_qt.h 0000644 0001750 0001750 00000006244 13561570220 016134 0 ustar otfried otfried // -*- C++ -*-
// --------------------------------------------------------------------
// A UI toolkit for Lua, QT version
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef IPEUI_QT_H
#define IPEUI_QT_H
#include "ipeui_common.h"
#include
#include
#include
#include
class QTimer;
class QTextEdit;
// --------------------------------------------------------------------
class MenuAction : public QAction {
Q_OBJECT
public:
MenuAction(const QString &name, int number,
const QString &item, const QString &text,
QWidget *parent);
QString name() const { return iName; }
QString itemName() const { return iItemName; }
int number() const { return iNumber; }
private:
QString iName;
QString iItemName;
int iNumber;
};
class PMenu : public Menu {
public:
PMenu(WINID parent);
virtual ~PMenu();
virtual int add(lua_State *L);
virtual int execute(lua_State *L);
private:
QMenu *iMenu;
};
// --------------------------------------------------------------------
class PTimer : public QObject, public Timer {
Q_OBJECT
public:
PTimer(lua_State *L0, int lua_object, const char *method);
virtual ~PTimer();
virtual int setInterval(lua_State *L);
virtual int active(lua_State *L);
virtual int start(lua_State *L);
virtual int stop(lua_State *L);
private:
QTimer *iTimer;
};
// --------------------------------------------------------------------
class PDialog : public QDialog, public Dialog {
Q_OBJECT
public:
PDialog(lua_State *L0, WINID parent, const char *caption);
~PDialog();
QGridLayout *gridlayout() { return iGrid; }
protected:
virtual void setMapped(lua_State *L, int idx);
virtual bool buildAndRun(int w, int h);
virtual void retrieveValues();
virtual void enableItem(int idx, bool value);
virtual void acceptDialog(lua_State *L);
protected:
virtual void keyPressEvent(QKeyEvent *e);
private:
std::vector iWidgets;
QGridLayout *iGrid;
QHBoxLayout *iButtonArea;
};
// --------------------------------------------------------------------
#endif
ipe-7.2.13/src/ipeui/ipeui_common.h 0000644 0001750 0001750 00000013425 13561570220 016777 0 ustar otfried otfried // --------------------------------------------------------------------
// Classes common to several platforms
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef IPEUI_COMMON_H
#define IPEUI_COMMON_H
extern "C" {
#include
#include
#include
}
#include
#include
#include
#include
#ifdef IPEUI_GTK
#include
using WINID = GtkWidget *;
#endif
#ifdef IPEUI_WIN32
#include
using WINID = HWND;
#endif
#ifdef IPEUI_QT
#include
using WINID = QWidget *;
#endif
#ifdef IPEUI_COCOA
#include
using WINID = NSWindow * __unsafe_unretained;
#endif
using ushort = unsigned short;
using uchar = unsigned char;
// --------------------------------------------------------------------
extern WINID check_winid(lua_State *L, int i);
extern void push_winid(lua_State *L, WINID win);
// --------------------------------------------------------------------
inline std::string checkstring(lua_State *L, int i) {
return std::string(luaL_checklstring(L, i, nullptr));
}
inline std::string tostring(lua_State *L, int i) {
return std::string(lua_tolstring(L, i, nullptr));
}
inline void luacall(lua_State *L, int nargs, int nresults) {
lua_callk(L, nargs, nresults, 0, nullptr);
}
// --------------------------------------------------------------------
class Dialog {
public:
Dialog(lua_State *L0, WINID parent, const char *caption);
virtual ~Dialog();
// Lua methods
int add(lua_State *L);
int addButton(lua_State *L);
int set(lua_State *L);
int get(lua_State *L);
int setEnabled(lua_State *L);
int setStretch(lua_State *L);
bool execute(lua_State *L, int w, int h);
virtual void acceptDialog(lua_State *L) = 0;
WINID winId() const { return hDialog; }
protected:
enum TFlags { ELogFile = 0x001, EXml = 0x002, ELatex = 0x040,
EAccept = 0x004, EReject = 0x008,
EReadOnly = 0x010, EDisabled = 0x020,
ESelectAll = 0x080, EFocused = 0x100,
ESpellCheck = 0x200,
};
enum TType { EButton = 0, ETextEdit, EList, ELabel, ECombo,
ECheckBox, EInput };
struct SElement {
std::string name;
TType type;
int row, col, rowspan, colspan;
int minWidth, minHeight; // only used on Windows
int lua_method;
int flags;
std::vector items;
std::string text;
int value;
};
void callLua(int luaMethod);
int findElement(lua_State *L, int index);
void setUnmapped(lua_State *L, int idx);
virtual void setMapped(lua_State *L, int idx) = 0;
virtual bool buildAndRun(int w, int h) = 0;
virtual void retrieveValues() = 0;
virtual void enableItem(int idx, bool value) = 0;
void addButtonItem(lua_State *L, SElement &m);
void addTextEdit(lua_State *L, SElement &m);
void addList(lua_State *L, SElement &m);
void addLabel(lua_State *L, SElement &m);
void addCombo(lua_State *L, SElement &m);
void addCheckbox(lua_State *L, SElement &m);
void addInput(lua_State *L, SElement &m);
void setListItems(lua_State *L, int index, SElement &m);
protected:
lua_State *L;
WINID iParent;
WINID hDialog;
std::string iCaption;
std::vector iElements;
int iLuaDialog;
bool iIgnoreEscape;
int iBaseX, iBaseY;
int iNoRows, iNoCols;
std::vector iRowStretch;
std::vector iColStretch;
};
// --------------------------------------------------------------------
inline Dialog **check_dialog(lua_State *L, int i)
{
return (Dialog **) luaL_checkudata(L, i, "Ipe.dialog");
}
// --------------------------------------------------------------------
class Menu {
public:
virtual ~Menu();
virtual int add(lua_State *L) = 0;
virtual int execute(lua_State *L) = 0;
};
// --------------------------------------------------------------------
inline Menu **check_menu(lua_State *L, int i)
{
return (Menu **) luaL_checkudata(L, i, "Ipe.menu");
}
// --------------------------------------------------------------------
class Timer {
public:
Timer(lua_State *L0, int lua_object, const char *method);
virtual ~Timer();
int setSingleShot(lua_State *L);
virtual int setInterval(lua_State *L) = 0;
virtual int active(lua_State *L) = 0;
virtual int start(lua_State *L) = 0;
virtual int stop(lua_State *L) = 0;
protected:
void callLua();
protected:
lua_State *L;
int iLuaObject;
std::string iMethod;
bool iSingleShot;
};
// --------------------------------------------------------------------
inline Timer **check_timer(lua_State *L, int i)
{
return (Timer **) luaL_checkudata(L, i, "Ipe.timer");
}
// --------------------------------------------------------------------
extern int luaopen_ipeui_common(lua_State *L);
// --------------------------------------------------------------------
#endif
ipe-7.2.13/src/ipeui/Makefile 0000644 0001750 0001750 00000002250 13561570220 015575 0 ustar otfried otfried # --------------------------------------------------------------------
# Makefile for ipeui
# --------------------------------------------------------------------
OBJDIR = $(BUILDDIR)/obj/ipeui
include ../common.mak
TARGET = $(call dll_target,ipeui)
MAKE_SYMLINKS = $(call dll_symlinks,ipeui)
SONAME = $(call soname,ipeui)
INSTALL_SYMLINKS = $(call install_symlinks,ipeui)
CPPFLAGS += $(UI_CFLAGS) $(LUA_CFLAGS)
LIBS += $(UI_LIBS) $(LUA_LIBS)
CXXFLAGS += $(DLL_CFLAGS)
all: $(TARGET)
sources = ipeui_common.cpp
cocoa_sources = ipeui_cocoa.cpp
qt_sources = ipeui_qt.cpp
moc_headers = ipeui_qt.h
win_sources = ipeui_win.cpp
gtk_sources = ipeui_gtk.cpp
ifdef IPEUI_COCOA
CXXFLAGS += $(IPEOBJCPP)
endif
$(TARGET): $(objects)
$(MAKE_LIBDIR)
$(CXX) $(LDFLAGS) $(DLL_LDFLAGS) $(SONAME) -o $@ $^ $(LIBS)
$(MAKE_SYMLINKS)
clean:
@-rm -f $(objects) $(TARGET) $(DEPEND)
$(DEPEND): Makefile
$(MAKE_DEPEND)
-include $(DEPEND)
install: $(TARGET)
$(INSTALL_DIR) $(INSTALL_ROOT)$(IPELIBDIR)
$(INSTALL_PROGRAMS) $(TARGET) $(INSTALL_ROOT)$(IPELIBDIR)
$(INSTALL_SYMLINKS)
# --------------------------------------------------------------------
ipe-7.2.13/src/ipeui/ipeui_common.cpp 0000644 0001750 0001750 00000044276 13561570220 017342 0 ustar otfried otfried // --------------------------------------------------------------------
// Lua bindings for UI, platform-neutral common part
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipeui_common.h"
#include
// --------------------------------------------------------------------
WINID check_winid(lua_State *L, int i)
{
if (lua_isnil(L, i))
return nullptr;
WINID *w = (WINID *) luaL_checkudata(L, i, "Ipe.winid");
return *w;
}
void push_winid(lua_State *L, WINID win)
{
WINID *w = (WINID *) lua_newuserdata(L, sizeof(WINID));
*w = win;
luaL_getmetatable(L, "Ipe.winid");
lua_setmetatable(L, -2);
}
static int winid_tostring(lua_State *L)
{
check_winid(L, 1);
lua_pushfstring(L, "GtkWidget@%p", lua_topointer(L, 1));
return 1;
}
static const struct luaL_Reg winid_methods[] = {
{ "__tostring", winid_tostring },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
Dialog::Dialog(lua_State *L0, WINID parent, const char *caption)
: iCaption(caption)
{
L = L0;
iParent = parent;
iLuaDialog = LUA_NOREF;
hDialog = nullptr;
iIgnoreEscape = false;
iNoRows = 1;
iNoCols = 1;
}
// Careful: On Qt, the dialog is owned by the parent window.
// It's important that Lua deletes the dialog before Qt tries to, otherwise
// we have a double deletion.
// Make sure not to create a cyclic reference to the dialog by capturing it inside
// the Lua method for an action, as this would stop it from being garbage collected.
Dialog::~Dialog()
{
// dereference lua methods
for (int i = 0; i < int(iElements.size()); ++i)
luaL_unref(L, LUA_REGISTRYINDEX, iElements[i].lua_method);
luaL_unref(L, LUA_REGISTRYINDEX, iLuaDialog);
}
void Dialog::callLua(int luaMethod)
{
// only call back to Lua during execute()
if (iLuaDialog == LUA_NOREF)
return;
lua_rawgeti(L, LUA_REGISTRYINDEX, luaMethod);
lua_rawgeti(L, LUA_REGISTRYINDEX, iLuaDialog);
luacall(L, 1, 0);
}
// name, label, action
int Dialog::addButton(lua_State *L)
{
SElement m;
m.name = std::string(luaL_checklstring(L, 2, nullptr));
m.type = EButton;
m.lua_method = LUA_NOREF;
m.flags = 0;
m.row = -1;
m.col = -1;
m.rowspan = 1;
m.colspan = 1;
m.text = std::string(luaL_checklstring(L, 3, nullptr));
if (lua_isstring(L, 4)) {
const char *s = lua_tolstring(L, 4, nullptr);
if (!strcmp(s, "accept"))
m.flags |= EAccept;
else if (!strcmp(s, "reject"))
m.flags |= EReject;
else
luaL_argerror(L, 4, "unknown action");
} else {
luaL_argcheck(L, lua_isfunction(L, 4), 4, "unknown action");
lua_pushvalue(L, 4);
m.lua_method = luaL_ref(L, LUA_REGISTRYINDEX);
}
m.minHeight = 16;
m.minWidth = 4 * m.text.length() + 8;
if (m.minWidth < 64)
m.minWidth = 64;
iElements.push_back(m);
return 0;
}
int Dialog::add(lua_State *L)
{
static const char * const typenames[] =
{ "button", "text", "list", "label", "combo", "checkbox", "input", nullptr };
SElement m;
m.name = checkstring(L, 2);
m.type = TType(luaL_checkoption(L, 3, nullptr, typenames));
luaL_checktype(L, 4, LUA_TTABLE);
m.lua_method = LUA_NOREF;
m.flags = 0;
m.row = luaL_checkinteger(L, 5) - 1;
if (m.row < 0)
m.row = iNoRows + 1 + m.row;
m.col = luaL_checkinteger(L, 6) - 1;
m.rowspan = 1;
m.colspan = 1;
if (!lua_isnoneornil(L, 7))
m.rowspan = luaL_checkinteger(L, 7);
if (!lua_isnoneornil(L, 8))
m.colspan = luaL_checkinteger(L, 8);
if (m.row + m.rowspan > iNoRows)
iNoRows = m.row + m.rowspan;
if (m.col + m.colspan > iNoCols)
iNoCols = m.col + m.colspan;
switch (m.type) {
case EButton:
addButtonItem(L, m);
break;
case ETextEdit:
addTextEdit(L, m);
break;
case EList:
addList(L, m);
break;
case ELabel:
addLabel(L, m);
break;
case ECombo:
addCombo(L, m);
break;
case ECheckBox:
addCheckbox(L, m);
break;
case EInput:
addInput(L, m);
break;
default:
break;
}
iElements.push_back(m);
return 0;
}
void Dialog::addLabel(lua_State *L, SElement &m)
{
lua_getfield(L, 4, "label");
luaL_argcheck(L, lua_isstring(L, -1), 4, "no label");
m.text = std::string(lua_tolstring(L, -1, nullptr));
lua_pop(L, 1); // label
m.minHeight = 16;
const char *p = m.text.c_str();
int maxlen = 0;
int curlen = 0;
while (*p) {
if (*p++ == '\n') {
m.minHeight += 8;
if (curlen > maxlen)
maxlen = curlen;
curlen = 0;
}
++curlen;
}
if (curlen > maxlen)
maxlen = curlen;
m.minWidth = 4 * maxlen;
}
void Dialog::addButtonItem(lua_State *L, SElement &m)
{
lua_getfield(L, 4, "label");
luaL_argcheck(L, lua_isstring(L, -1), 4, "no button label");
m.text = tostring(L, -1);
lua_getfield(L, 4, "action");
if (lua_isstring(L, -1)) {
auto s = tostring(L, -1);
if (s == "accept")
m.flags |= EAccept;
else if (s == "reject")
m.flags |= EReject;
else
luaL_argerror(L, 4, "unknown action");
} else if (lua_isfunction(L, -1)) {
lua_pushvalue(L, -1);
m.lua_method = luaL_ref(L, LUA_REGISTRYINDEX);
} else if (!lua_isnil(L, -1))
luaL_argerror(L, 4, "unknown action type");
lua_pop(L, 2); // action, label
m.minHeight = 16;
m.minWidth = 4 * m.text.length() + 8;
if (m.minWidth < 64)
m.minWidth = 64;
}
void Dialog::addCheckbox(lua_State *L, SElement &m)
{
lua_getfield(L, 4, "label");
luaL_argcheck(L, lua_isstring(L, -1), 4, "no label");
m.text = tostring(L, -1);
lua_getfield(L, 4, "action");
if (!lua_isnil(L, -1)) {
luaL_argcheck(L, lua_isfunction(L, -1), 4, "unknown action type");
lua_pushvalue(L, -1);
m.lua_method = luaL_ref(L, LUA_REGISTRYINDEX);
}
lua_pop(L, 2); // action, label
m.value = 0;
m.minHeight = 16;
m.minWidth = 4 * m.text.length() + 32;
}
void Dialog::addInput(lua_State *L, SElement &m)
{
m.minHeight = 12;
m.minWidth = 100;
lua_getfield(L, 4, "select_all");
if (lua_toboolean(L, -1))
m.flags |= ESelectAll;
lua_getfield(L, 4, "focus");
if (lua_toboolean(L, -1))
m.flags |= EFocused;
lua_pop(L, 2);
}
void Dialog::addTextEdit(lua_State *L, SElement &m)
{
lua_getfield(L, 4, "read_only");
if (lua_toboolean(L, -1))
m.flags |= EReadOnly;
lua_getfield(L, 4, "select_all");
if (lua_toboolean(L, -1))
m.flags |= ESelectAll;
lua_getfield(L, 4, "focus");
if (lua_toboolean(L, -1))
m.flags |= EFocused;
lua_getfield(L, 4, "syntax");
if (!lua_isnil(L, -1)) {
auto s = tostring(L, -1);
if (s == "logfile") {
m.flags |= ELogFile;
} else if (s == "xml") {
m.flags |= EXml;
} else if (s == "latex") {
m.flags |= ELatex;
} else
luaL_argerror(L, 4, "unknown syntax");
}
lua_getfield(L, 4, "spell_check");
if (lua_toboolean(L, -1))
m.flags |= ESpellCheck;
lua_pop(L, 5); // spell_check, syntax, focus, select_all, read_only
m.minHeight = 48;
m.minWidth = 100;
}
void Dialog::setListItems(lua_State *L, int index, SElement &m)
{
int no = lua_rawlen(L, index);
m.minWidth = 48;
for (int i = 1; i <= no; ++i) {
lua_rawgeti(L, index, i);
luaL_argcheck(L, lua_isstring(L, -1), index, "items must be strings");
auto item = tostring(L, -1);
int w = 4 * item.size() + 16;
if (w > m.minWidth)
m.minWidth = w;
m.items.emplace_back(item);
lua_pop(L, 1); // item
}
lua_getfield(L, index, "action");
if (!lua_isnil(L, -1)) {
luaL_argcheck(L, lua_isfunction(L, -1), index, "unknown action type");
lua_pushvalue(L, -1);
m.lua_method = luaL_ref(L, LUA_REGISTRYINDEX);
}
lua_pop(L, 1); // action
}
void Dialog::addList(lua_State *L, SElement &m)
{
setListItems(L, 4, m);
m.value = 0;
m.minHeight = 48;
}
void Dialog::addCombo(lua_State *L, SElement &m)
{
setListItems(L, 4, m);
m.value = 0;
m.minHeight = 16;
}
int Dialog::findElement(lua_State *L, int index)
{
auto name = checkstring(L, index);
for (int i = 0; i < int(iElements.size()); ++i) {
if (name == iElements[i].name)
return i;
}
return luaL_argerror(L, index, "no such element in dialog");
}
int Dialog::set(lua_State *L)
{
auto s = checkstring(L, 2);
if (s == "ignore-escape") {
iIgnoreEscape = lua_toboolean(L, 3);
return 0;
}
int idx = findElement(L, 2);
// set internal representation
setUnmapped(L, idx);
// if dialog is on screen, also set there
if (iLuaDialog != LUA_NOREF)
setMapped(L, idx);
return 0;
}
void Dialog::setUnmapped(lua_State *L, int idx)
{
SElement &m = iElements[idx];
switch (m.type) {
case ELabel:
case ETextEdit:
case EInput:
m.text = checkstring(L, 3);
break;
case EList:
case ECombo:
if (lua_isnumber(L, 3)) {
int n = luaL_checkinteger(L, 3);
luaL_argcheck(L, 1 <= n && n <= int(m.items.size()), 3,
"list index out of bounds");
m.value = n - 1;
} else if (lua_isstring(L, 3)) {
auto s = tostring(L, 3);
auto it = std::find_if(m.items.cbegin(), m.items.cend(),
[&s](const std::string &el) { return s == el; });
luaL_argcheck(L, it != m.items.end(), 3, "item not in list");
m.value = (it - m.items.begin());
} else {
luaL_checktype(L, 3, LUA_TTABLE);
m.items.clear();
setListItems(L, 3, m);
m.value = 0;
}
break;
case ECheckBox:
m.value = lua_toboolean(L, 3);
break;
default:
luaL_argerror(L, 2, "no suitable element");
}
}
int Dialog::get(lua_State *L)
{
if (iLuaDialog != LUA_NOREF)
retrieveValues();
int idx = findElement(L, 2);
SElement &m = iElements[idx];
switch (m.type) {
case ETextEdit:
case EInput:
lua_pushstring(L, m.text.c_str());
return 1;
case EList:
case ECombo:
lua_pushinteger(L, m.value + 1);
return 1;
case ECheckBox:
lua_pushboolean(L, m.value);
return 1;
default:
return luaL_argerror(L, 2, "no suitable element");
}
}
bool Dialog::execute(lua_State *L, int w, int h)
{
// remember Lua object for dialog for callbacks
lua_pushvalue(L, 1);
iLuaDialog = luaL_ref(L, LUA_REGISTRYINDEX);
bool result = buildAndRun(w, h); // execute dialog
// discard reference to dialog object
// (otherwise the circular reference will stop Lua gc)
luaL_unref(L, LUA_REGISTRYINDEX, iLuaDialog);
iLuaDialog = LUA_NOREF;
return result;
}
int Dialog::setEnabled(lua_State *L)
{
int idx = findElement(L, 2);
bool value = lua_toboolean(L, 3);
if (iLuaDialog != LUA_NOREF) // mapped
enableItem(idx, value);
else if (value)
iElements[idx].flags &= ~EDisabled;
else
iElements[idx].flags |= EDisabled;
return 0;
}
int Dialog::setStretch(lua_State *L)
{
static const char * const typenames[] = { "row", "column", nullptr };
while (int(iColStretch.size()) < iNoCols)
iColStretch.push_back(0);
while (int(iRowStretch.size()) < iNoRows)
iRowStretch.push_back(0);
int type = luaL_checkoption(L, 2, nullptr, typenames);
int rowcol = (int)luaL_checkinteger(L, 3) - 1;
int stretch = (int)luaL_checkinteger(L, 4);
if (type == 0) {
luaL_argcheck(L, 0 <= rowcol && rowcol < iNoRows, 3,
"Row index out of range");
iRowStretch[rowcol] = stretch;
} else {
luaL_argcheck(L, 0 <= rowcol && rowcol < iNoCols, 3,
"Column index out of range");
iColStretch[rowcol] = stretch;
}
return 0;
}
// --------------------------------------------------------------------
static int dialog_tostring(lua_State *L)
{
check_dialog(L, 1);
lua_pushfstring(L, "Dialog@%p", lua_topointer(L, 1));
return 1;
}
static int dialog_destructor(lua_State *L)
{
//fprintf(stderr, "Dialog::~Dialog()\n");
Dialog **dlg = check_dialog(L, 1);
// on Qt, hope we destruct before the parent window does
delete (*dlg);
*dlg = nullptr;
return 0;
}
static int dialog_execute(lua_State *L)
{
Dialog **dlg = check_dialog(L, 1);
int w = 0;
int h = 0;
if (!lua_isnoneornil(L, 2)) {
luaL_argcheck(L, lua_istable(L, 2), 2, "argument is not a table");
lua_rawgeti(L, 2, 1);
luaL_argcheck(L, lua_isnumber(L, -1), 2, "width is not a number");
lua_rawgeti(L, 2, 2);
luaL_argcheck(L, lua_isnumber(L, -1), 2, "height is not a number");
w = lua_tointegerx(L, -2, nullptr);
h = lua_tointegerx(L, -1, nullptr);
lua_pop(L, 2); // w & h
}
lua_pushboolean(L, (*dlg)->execute(L, w, h));
return 1;
}
static int dialog_setStretch(lua_State *L)
{
Dialog **dlg = check_dialog(L, 1);
return (*dlg)->setStretch(L);
}
static int dialog_add(lua_State *L)
{
Dialog **dlg = check_dialog(L, 1);
return (*dlg)->add(L);
}
static int dialog_addButton(lua_State *L)
{
Dialog **dlg = check_dialog(L, 1);
return (*dlg)->addButton(L);
}
static int dialog_set(lua_State *L)
{
Dialog **dlg = check_dialog(L, 1);
return (*dlg)->set(L);
}
static int dialog_get(lua_State *L)
{
Dialog **dlg = check_dialog(L, 1);
return (*dlg)->get(L);
}
static int dialog_setEnabled(lua_State *L)
{
Dialog **dlg = check_dialog(L, 1);
return (*dlg)->setEnabled(L);
}
static int dialog_accept(lua_State *L)
{
Dialog **dlg = check_dialog(L, 1);
(*dlg)->acceptDialog(L);
return 0;
}
// --------------------------------------------------------------------
static const struct luaL_Reg dialog_methods[] = {
{ "__tostring", dialog_tostring },
{ "__gc", dialog_destructor },
{ "execute", dialog_execute },
{ "setStretch", dialog_setStretch },
{ "add", dialog_add },
{ "addButton", dialog_addButton },
{ "set", dialog_set },
{ "get", dialog_get },
{ "setEnabled", dialog_setEnabled },
{ "accept", dialog_accept },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
Menu::~Menu()
{
// empty
}
// --------------------------------------------------------------------
static int menu_tostring(lua_State *L)
{
check_menu(L, 1);
lua_pushfstring(L, "Menu@%p", lua_topointer(L, 1));
return 1;
}
static int menu_destructor(lua_State *L)
{
//fprintf(stderr, "Menu::~Menu()\n");
Menu **m = check_menu(L, 1);
delete *m;
*m = nullptr;
return 0;
}
static int menu_execute(lua_State *L)
{
Menu **m = check_menu(L, 1);
return (*m)->execute(L);
}
static int menu_add(lua_State *L)
{
Menu **m = check_menu(L, 1);
return (*m)->add(L);
}
// --------------------------------------------------------------------
static const struct luaL_Reg menu_methods[] = {
{ "__tostring", menu_tostring },
{ "__gc", menu_destructor },
{ "execute", menu_execute },
{ "add", menu_add },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
Timer::Timer(lua_State *L0, int lua_object, const char *method)
: iMethod(method)
{
L = L0;
iLuaObject = lua_object;
iSingleShot = false;
}
Timer::~Timer()
{
luaL_unref(L, LUA_REGISTRYINDEX, iLuaObject);
}
void Timer::callLua()
{
lua_rawgeti(L, LUA_REGISTRYINDEX, iLuaObject);
lua_rawgeti(L, -1, 1); // get Lua object
if (lua_isnil(L, -1)) {
lua_pop(L, 2); // pop weak table, nil
return;
}
lua_getfield(L, -1, iMethod.c_str());
if (lua_isnil(L, -1)) {
lua_pop(L, 3); // pop weak table, table, nil
return;
}
lua_remove(L, -3); // remove weak table
lua_insert(L, -2); // stack is now: method, table
luacall(L, 1, 0); // call method
}
int Timer::setSingleShot(lua_State *L)
{
iSingleShot = lua_toboolean(L, 2);
return 0;
}
// --------------------------------------------------------------------
static int timer_tostring(lua_State *L)
{
check_timer(L, 1);
lua_pushfstring(L, "Timer@%p", lua_topointer(L, 1));
return 1;
}
static int timer_destructor(lua_State *L)
{
Timer **t = check_timer(L, 1);
//fprintf(stderr, "Timer::~Timer()\n");
delete *t;
*t = nullptr;
return 0;
}
// --------------------------------------------------------------------
static int timer_start(lua_State *L)
{
Timer **t = check_timer(L, 1);
return (*t)->start(L);
}
static int timer_stop(lua_State *L)
{
Timer **t = check_timer(L, 1);
return (*t)->stop(L);
}
static int timer_active(lua_State *L)
{
Timer **t = check_timer(L, 1);
return (*t)->active(L);
}
static int timer_setinterval(lua_State *L)
{
Timer **t = check_timer(L, 1);
return (*t)->setInterval(L);
}
static int timer_setsingleshot(lua_State *L)
{
Timer **t = check_timer(L, 1);
return (*t)->setSingleShot(L);
}
// --------------------------------------------------------------------
static const struct luaL_Reg timer_methods[] = {
{ "__tostring", timer_tostring },
{ "__gc", timer_destructor },
{ "start", timer_start },
{ "stop", timer_stop },
{ "active", timer_active },
{ "setInterval", timer_setinterval },
{ "setSingleShot", timer_setsingleshot },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
static void make_metatable(lua_State *L, const char *name,
const struct luaL_Reg *methods)
{
luaL_newmetatable(L, name);
lua_pushstring(L, "__index");
lua_pushvalue(L, -2); /* pushes the metatable */
lua_settable(L, -3); /* metatable.__index = metatable */
luaL_setfuncs(L, methods, 0);
lua_pop(L, 1);
}
int luaopen_ipeui_common(lua_State *L)
{
make_metatable(L, "Ipe.winid", winid_methods);
make_metatable(L, "Ipe.dialog", dialog_methods);
make_metatable(L, "Ipe.menu", menu_methods);
make_metatable(L, "Ipe.timer", timer_methods);
return 0;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipeui/ipeui_win.cpp 0000644 0001750 0001750 00000077777 13561570220 016663 0 ustar otfried otfried // --------------------------------------------------------------------
// Lua bindings for Win32 dialogs
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipeui_common.h"
#include "ipeui_wstring.h"
#include
// --------------------------------------------------------------------
#define IDBASE 9000
#define PAD 3
#define BORDER 6
#define BUTTONHEIGHT 14
// --------------------------------------------------------------------
void WString::init(const char *s, int len)
{
int rw = MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
resize(rw + 1, wchar_t(0));
MultiByteToWideChar(CP_UTF8, 0, s, len, data(), rw);
}
BOOL setWindowText(HWND h, const char *s)
{
WString ws(s);
wchar_t *p = ws.data();
std::vector w;
w.reserve(ws.size());
while (*p) {
wchar_t ch = *p++;
if (ch != '\r') {
if (ch == '\n')
w.push_back('\r');
w.push_back(ch);
}
}
w.push_back(0);
return SetWindowTextW(h, &w[0]);
}
void sendMessage(HWND h, UINT code, const char *t, WPARAM wParam)
{
WString w(t);
SendMessageW(h, code, wParam, (LPARAM) w.data());
}
static std::string wideToUtf8(const wchar_t *wbuf)
{
int rm = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, nullptr, 0, nullptr, nullptr);
std::vector multi(rm);
WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, &multi[0], rm, nullptr, nullptr);
return std::string(&multi[0]);
}
static void buildFlags(std::vector &t, DWORD flags)
{
union {
DWORD dw;
short s[2];
} a;
a.dw = flags;
t.push_back(a.s[0]);
t.push_back(a.s[1]);
t.push_back(0);
t.push_back(0);
}
static void buildString(std::vector &t, const char *s)
{
WString w(s);
const wchar_t *p = w.data();
while (*p)
t.push_back(*p++);
t.push_back(0);
}
static void buildControl(std::vector &t, short what, const char *s = nullptr)
{
t.push_back(0xffff);
t.push_back(what);
if (s)
buildString(t, s);
else
t.push_back(0); // text
t.push_back(0); // creation data
}
// --------------------------------------------------------------------
class PDialog : public Dialog {
public:
PDialog(lua_State *L0, WINID parent, const char *caption);
virtual ~PDialog();
protected:
virtual void setMapped(lua_State *L, int idx);
virtual bool buildAndRun(int w, int h);
void buildElements(std::vector &t);
virtual void retrieveValues();
virtual void enableItem(int idx, bool value);
void computeDimensions(int &w, int &h);
void buildDimensions(std::vector &t, SElement &m, int id);
virtual void acceptDialog(lua_State *L);
BOOL initDialog();
BOOL dlgCommand(WPARAM wParam, LPARAM lParam);
static BOOL CALLBACK dialogProc(HWND hwndDlg, UINT message,
WPARAM wParam, LPARAM lParam);
private:
int iBaseX, iBaseY;
int iButtonX;
std::vector iRowHeight;
std::vector iColWidth;
};
PDialog::PDialog(lua_State *L0, WINID parent, const char *caption)
: Dialog(L0, parent, caption)
{
LONG base = GetDialogBaseUnits();
iBaseX = LOWORD(base);
iBaseY = HIWORD(base);
}
PDialog::~PDialog()
{
// nothing yet
}
void PDialog::setMapped(lua_State *L, int idx)
{
SElement &m = iElements[idx];
HWND h = GetDlgItem(hDialog, idx+IDBASE);
switch (m.type) {
case ETextEdit:
case EInput:
case ELabel:
setWindowText(h, m.text.c_str());
break;
case EList:
if (!lua_isnumber(L, 3)) {
ListBox_ResetContent(h);
for (int j = 0; j < int(m.items.size()); ++j)
sendMessage(h, LB_ADDSTRING, m.items[j].c_str());
}
ListBox_SetCurSel(h, m.value);
break;
case ECombo:
if (!lua_isnumber(L, 3)) {
ComboBox_ResetContent(h);
for (int j = 0; j < int(m.items.size()); ++j)
sendMessage(h, CB_ADDSTRING, m.items[j].c_str());
}
ComboBox_SetCurSel(h, m.value);
break;
case ECheckBox:
CheckDlgButton(hDialog, idx+IDBASE,
(m.value ? BST_CHECKED : BST_UNCHECKED));
break;
default:
break; // already handled in setUnmapped
}
}
static void markupLog(HWND h, const std::string &text)
{
const char *p = text.c_str();
if (*p) ++p;
int pos = 1;
while (*p) {
if (p[-1] == '\n' && p[0] == '!') {
// found it
const char *b = p;
while (*p && *p != '\n')
++p;
SendMessage(h, EM_SETSEL, (WPARAM) pos, (LPARAM) pos + (p - b));
int line = SendMessage(h, EM_LINEFROMCHAR, (WPARAM) pos, 0);
SendMessage(h, EM_LINESCROLL, 0, (LPARAM) line - 1);
return;
}
// take into account conversion done by setText
if (*p == '\n')
pos += 2;
else if (*p != '\r')
++pos;
++p;
}
// not found, nothing to be done
}
BOOL PDialog::initDialog()
{
BOOL result = TRUE;
for (int i = 0; i < int(iElements.size()); ++i) {
SElement &m = iElements[i];
HWND h = GetDlgItem(hDialog, i+IDBASE);
if (m.flags & EDisabled)
EnableWindow(h, FALSE);
switch (m.type) {
case EInput:
case ETextEdit:
setWindowText(h, m.text.c_str());
if (m.flags & EFocused) {
SetFocus(h);
result = FALSE; // we set the focus ourselves
}
if (m.flags & ELogFile)
markupLog(h, m.text);
break;
case EList:
for (int j = 0; j < int(m.items.size()); ++j)
sendMessage(h, LB_ADDSTRING, m.items[j].c_str());
ListBox_SetCurSel(h, m.value);
break;
case ECombo:
for (int j = 0; j < int(m.items.size()); ++j)
sendMessage(h, CB_ADDSTRING, m.items[j].c_str());
ComboBox_SetCurSel(h, m.value);
break;
case ECheckBox:
CheckDlgButton(hDialog, i+IDBASE,
(m.value ? BST_CHECKED : BST_UNCHECKED));
break;
default:
break;
}
}
return result;
}
static std::string getEditText(HWND h)
{
int n = GetWindowTextLengthW(h);
if (n == 0)
return std::string("");
WCHAR wbuf[n+1];
GetWindowTextW(h, wbuf, n+1);
std::vector w;
wchar_t *p = wbuf;
while (*p) {
wchar_t ch = *p++;
if (ch != '\r')
w.push_back(ch);
}
w.push_back(0);
return wideToUtf8(&w[0]);
}
void PDialog::retrieveValues()
{
for (int i = 0; i < int(iElements.size()); ++i) {
SElement &m = iElements[i];
HWND h = GetDlgItem(hDialog, i+IDBASE);
switch (m.type) {
case ETextEdit:
case EInput:
m.text = getEditText(h);
break;
case EList:
m.value = ListBox_GetCurSel(h);
if (m.value == LB_ERR)
m.value = 0;
break;
case ECombo:
m.value = ComboBox_GetCurSel(h);
if (m.value == CB_ERR)
m.value = 0;
break;
case ECheckBox:
m.value = (IsDlgButtonChecked(hDialog, i+IDBASE) == BST_CHECKED);
break;
case ELabel:
default:
break; // nothing to do
}
}
}
void PDialog::buildDimensions(std::vector &t, SElement &m, int id)
{
int x = BORDER;
int y = BORDER;
int w = 0;
int h = 0;
if (m.row < 0) {
y = BORDER;
for (int i = 0; i < iNoRows; ++i)
y += iRowHeight[i] + PAD;
w = m.minWidth;
h = BUTTONHEIGHT;
x = iButtonX;
iButtonX += w + PAD;
} else {
for (int i = 0; i < m.col; ++i)
x += iColWidth[i] + PAD;
for (int i = 0; i < m.row; ++i)
y += iRowHeight[i] + PAD;
w = iColWidth[m.col];
for (int i = m.col + 1; i < m.col + m.colspan; ++i)
w += iColWidth[i] + PAD;
h = iRowHeight[m.row];
for (int i = m.row + 1; i < m.row + m.rowspan; ++i)
h += iRowHeight[i] + PAD;
}
t.push_back(x);
t.push_back(y);
t.push_back(w);
t.push_back(h);
t.push_back(id); // control id
}
BOOL PDialog::dlgCommand(WPARAM wParam, LPARAM lParam)
{
int id = LOWORD(wParam);
if (id == IDOK) {
retrieveValues();
EndDialog(hDialog, TRUE);
return TRUE;
}
if (id == IDCANCEL && !iIgnoreEscape) {
retrieveValues();
EndDialog(hDialog, FALSE);
return TRUE;
}
if (id < IDBASE || id >= IDBASE + int(iElements.size()))
return FALSE;
SElement &m = iElements[id - IDBASE];
if (m.flags & EAccept) {
retrieveValues();
EndDialog(hDialog, TRUE);
return TRUE;
} else if (m.flags & EReject) {
retrieveValues();
EndDialog(hDialog, FALSE);
return TRUE;
} else if (m.lua_method != LUA_NOREF)
callLua(m.lua_method);
return FALSE;
}
void PDialog::acceptDialog(lua_State *L)
{
int accept = lua_toboolean(L, 2);
retrieveValues();
EndDialog(hDialog, accept);
}
static WNDPROC wpOrigProc;
static LRESULT subclassProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
if (message == WM_KEYDOWN) {
if (wParam == VK_RETURN && (GetKeyState(VK_CONTROL) & 0x8000)) {
SendMessage(GetParent(hwnd), WM_COMMAND, IDOK, 0);
return 0;
}
}
return CallWindowProc(wpOrigProc, hwnd, message, wParam, lParam);
}
BOOL CALLBACK PDialog::dialogProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
PDialog *d = (PDialog *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
switch (message) {
case WM_INITDIALOG:
d = (PDialog *) lParam;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) d);
d->hDialog = hwnd;
// subclass all Edit controls
// TODO: Use modern SetSubclass method
for (int i = 0; i < int(d->iElements.size()); ++i) {
if (d->iElements[i].type == ETextEdit)
wpOrigProc = (WNDPROC)
SetWindowLongPtr(GetDlgItem(hwnd, i + IDBASE),
GWLP_WNDPROC, (LONG_PTR) &subclassProc);
}
return d->initDialog();
case WM_COMMAND:
if (d)
return d->dlgCommand(wParam, lParam);
else
return FALSE;
case WM_DESTROY:
// Remove the subclasses from text edits
for (int i = 0; i < int(d->iElements.size()); ++i) {
if (d->iElements[i].type == ETextEdit)
SetWindowLongPtr(GetDlgItem(hwnd, i + IDBASE),
GWLP_WNDPROC, (LONG_PTR) wpOrigProc);
}
return FALSE;
default:
return FALSE;
}
}
void PDialog::buildElements(std::vector &t)
{
for (int i = 0; i < int(iElements.size()); ++i) {
if (t.size() % 2 != 0)
t.push_back(0);
SElement &m = iElements[i];
int id = i + IDBASE;
DWORD flags = WS_CHILD|WS_VISIBLE;
switch (m.type) {
case EButton:
flags |= BS_TEXT|WS_TABSTOP|BS_FLAT;
if (m.flags & EAccept)
flags |= BS_DEFPUSHBUTTON;
else
flags |= BS_PUSHBUTTON;
buildFlags(t, flags);
buildDimensions(t, m, id);
buildControl(t, 0x0080, m.text.c_str()); // button
break;
case ECheckBox:
buildFlags(t, flags|BS_AUTOCHECKBOX|BS_TEXT|WS_TABSTOP);
buildDimensions(t, m, id);
buildControl(t, 0x0080, m.text.c_str()); // button
break;
case ELabel:
buildFlags(t, flags|SS_LEFT);
buildDimensions(t, m, id);
buildControl(t, 0x0082, m.text.c_str()); // static text
break;
case EInput:
buildFlags(t, flags|ES_LEFT|WS_TABSTOP|WS_BORDER|ES_AUTOHSCROLL);
buildDimensions(t, m, id);
buildControl(t, 0x0081); // edit
break;
case ETextEdit:
flags |= ES_LEFT|WS_TABSTOP|WS_BORDER;
flags |= ES_MULTILINE|ES_WANTRETURN|WS_VSCROLL;
if (m.flags & EReadOnly)
flags |= ES_READONLY;
buildFlags(t, flags);
buildDimensions(t, m, id);
buildControl(t, 0x0081); // edit
break;
case EList:
buildFlags(t, flags|WS_TABSTOP|WS_VSCROLL|WS_BORDER);
buildDimensions(t, m, id);
buildControl(t, 0x0083); // list box
break;
case ECombo:
buildFlags(t, flags|CBS_DROPDOWNLIST|WS_TABSTOP);
buildDimensions(t, m, id);
buildControl(t, 0x0085); // combo box
break;
default:
break;
}
}
}
bool PDialog::buildAndRun(int w, int h)
{
computeDimensions(w, h);
RECT rect;
GetWindowRect(iParent, &rect);
int pw = (rect.right - rect.left) * 4 / iBaseX;
int ph = (rect.bottom - rect.top) * 8 / iBaseY;
std::vector t;
// Dialog flags
buildFlags(t, WS_POPUP | WS_BORDER | DS_SHELLFONT |
WS_SYSMENU | DS_MODALFRAME | WS_CAPTION);
t.push_back(iElements.size());
t.push_back((pw - w)/2); // offset of popup-window from parent window
t.push_back((ph - h)/2);
t.push_back(w);
t.push_back(h);
// menu
t.push_back(0);
// class
t.push_back(0);
// title
buildString(t, iCaption.c_str());
// for DS_SHELLFONT
t.push_back(10);
buildString(t,"MS Shell Dlg");
buildElements(t);
int res =
DialogBoxIndirectParamW((HINSTANCE) GetWindowLongPtr(iParent,
GWLP_HINSTANCE),
(LPCDLGTEMPLATE) &t[0],
iParent, (DLGPROC) dialogProc, (LPARAM) this);
// retrieveValues() has been called before EndDialog!
hDialog = nullptr; // already destroyed by Windows
return (res > 0);
}
void PDialog::computeDimensions(int &w, int &h)
{
int minWidth[iNoCols];
int minHeight[iNoRows];
for (int i = 0; i < iNoCols; ++i)
minWidth[i] = 0;
for (int i = 0; i < iNoRows; ++i)
minHeight[i] = 0;
int buttonWidth = -PAD;
for (int i = 0; i < int(iElements.size()); ++i) {
SElement &m = iElements[i];
if (m.row < 0) { // button row
buttonWidth += m.minWidth + PAD;
} else {
int wd = m.minWidth / m.colspan;
for (int j = m.col; j < m.col + m.colspan; ++j) {
if (wd > minWidth[j])
minWidth[j] = wd;
}
int ht = m.minHeight / m.rowspan;
for (int j = m.row; j < m.row + m.rowspan; ++j) {
if (ht > minHeight[j])
minHeight[j] = ht;
}
}
}
// Convert w and h to dialog units:
w = w * 4 / iBaseX;
h = h * 8 / iBaseY;
while (int(iColStretch.size()) < iNoCols)
iColStretch.push_back(0);
while (int(iRowStretch.size()) < iNoRows)
iRowStretch.push_back(0);
int totalW = BORDER + BORDER - PAD;
int totalWStretch = 0;
for (int i = 0; i < iNoCols; ++i) {
totalW += minWidth[i] + PAD;
totalWStretch += iColStretch[i];
}
int totalH = BORDER + BORDER + BUTTONHEIGHT;
int totalHStretch = 0;
for (int i = 0; i < iNoRows; ++i) {
totalH += minHeight[i] + PAD;
totalHStretch += iRowStretch[i];
}
if (totalW > w)
w = totalW;
if (totalH > h)
h = totalH;
if (buttonWidth + BORDER + BORDER > w)
w = buttonWidth + BORDER + BORDER;
iButtonX = (w - buttonWidth) / 2;
int spareW = w - totalW;
int spareH = h - totalH;
iColWidth.resize(iNoCols);
iRowHeight.resize(iNoRows);
if (totalWStretch == 0) {
// spread spareW equally
int extra = spareW / iNoCols;
for (int i = 0; i < iNoCols; ++i)
iColWidth[i] = minWidth[i] + extra;
} else {
for (int i = 0; i < iNoCols; ++i) {
int extra = spareW * iColStretch[i] / totalWStretch;
iColWidth[i] = minWidth[i] + extra;
}
}
if (totalHStretch == 0) {
// spread spareH equally
int extra = spareH / iNoRows;
for (int i = 0; i < iNoRows; ++i)
iRowHeight[i] = minHeight[i] + extra;
} else {
for (int i = 0; i < iNoRows; ++i) {
int extra = spareH * iRowStretch[i] / totalHStretch;
iRowHeight[i] = minHeight[i] + extra;
}
}
/*
fprintf(stderr, "iColWidth\n");
for (int i = 0; i < iNoCols; ++i)
fprintf(stderr, "%d ", iColWidth[i]);
fprintf(stderr, "\n");
fflush(stderr);
*/
}
void PDialog::enableItem(int idx, bool value)
{
EnableWindow(GetDlgItem(hDialog, idx+IDBASE), value);
}
// --------------------------------------------------------------------
static int dialog_constructor(lua_State *L)
{
HWND parent = check_winid(L, 1);
const char *s = luaL_checklstring(L, 2, nullptr);
Dialog **dlg = (Dialog **) lua_newuserdata(L, sizeof(Dialog *));
*dlg = nullptr;
luaL_getmetatable(L, "Ipe.dialog");
lua_setmetatable(L, -2);
*dlg = new PDialog(L, parent, s);
return 1;
}
// --------------------------------------------------------------------
class PMenu : public Menu {
public:
PMenu(HWND parent);
virtual ~PMenu();
virtual int add(lua_State *L);
virtual int execute(lua_State *L);
private:
struct Item {
std::string name;
std::string itemName;
int itemIndex;
};
std::vector- items;
int currentId;
HMENU hMenu;
HWND hwnd;
std::vector bitmaps;
};
PMenu::PMenu(HWND parent)
{
hMenu = CreatePopupMenu();
hwnd = parent;
}
PMenu::~PMenu()
{
if (hMenu)
DestroyMenu(hMenu);
hMenu = nullptr;
for (int i = 0; i < int(bitmaps.size()); ++i)
DeleteObject(bitmaps[i]);
}
int PMenu::execute(lua_State *L)
{
int vx = (int)luaL_checkinteger(L, 2);
int vy = (int)luaL_checkinteger(L, 3);
int result = TrackPopupMenu(hMenu,
TPM_NONOTIFY | TPM_RETURNCMD | TPM_RIGHTBUTTON,
vx, vy, 0, hwnd, nullptr);
if (1 <= result && result <= int(items.size())) {
result -= 1;
lua_pushstring(L, items[result].name.c_str());
lua_pushinteger(L, items[result].itemIndex);
if (items[result].itemName.c_str())
lua_pushstring(L, items[result].itemName.c_str());
else
lua_pushstring(L, "");
return 3;
}
return 0;
}
static HBITMAP colorIcon(double red, double green, double blue)
{
int r = int(red * 255.0);
int g = int(green * 255.0);
int b = int(blue * 255.0);
COLORREF rgb = RGB(r, g, b);
int cx = GetSystemMetrics(SM_CXMENUCHECK);
int cy = GetSystemMetrics(SM_CYMENUCHECK);
HDC hdc = GetDC(nullptr);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP bm = CreateCompatibleBitmap(hdc, cx, cy);
SelectObject(memDC, bm);
for (int y = 0; y < cy; ++y) {
for (int x = 0; x < cx; ++x) {
SetPixel(memDC, x, y, rgb);
}
}
DeleteDC(memDC);
ReleaseDC(nullptr, hdc);
return bm;
}
int PMenu::add(lua_State *L)
{
const char *name = luaL_checklstring(L, 2, nullptr);
WString title(luaL_checklstring(L, 3, nullptr));
if (lua_gettop(L) == 3) {
AppendMenuW(hMenu, MF_STRING, items.size() + 1, title.data());
Item item;
item.name = name;
item.itemIndex = 0;
items.push_back(item);
} else {
luaL_argcheck(L, lua_istable(L, 4), 4, "argument is not a table");
bool hasmap = !lua_isnoneornil(L, 5) && lua_isfunction(L, 5);
bool hastable = !hasmap && !lua_isnoneornil(L, 5);
bool hascolor = !lua_isnoneornil(L, 6) && lua_isfunction(L, 6);
bool hascheck = !hascolor && !lua_isnoneornil(L, 6);
if (hastable)
luaL_argcheck(L, lua_istable(L, 5), 5,
"argument is not a function or table");
const char *current = nullptr;
if (hascheck) {
luaL_argcheck(L, lua_isstring(L, 6), 6,
"argument is not a function or string");
current = luaL_checklstring(L, 6, nullptr);
}
int no = lua_rawlen(L, 4);
HMENU sm = CreatePopupMenu();
for (int i = 1; i <= no; ++i) {
lua_rawgeti(L, 4, i);
luaL_argcheck(L, lua_isstring(L, -1), 4, "items must be strings");
int id = items.size() + 1;
const char *item = lua_tolstring(L, -1, nullptr);
if (hastable) {
lua_rawgeti(L, 5, i);
luaL_argcheck(L, lua_isstring(L, -1), 5, "labels must be strings");
} else if (hasmap) {
lua_pushvalue(L, 5); // function
lua_pushnumber(L, i); // index
lua_pushvalue(L, -3); // name
luacall(L, 2, 1); // function returns label
luaL_argcheck(L, lua_isstring(L, -1), 5,
"function does not return string");
} else
lua_pushvalue(L, -1);
WString text(tostring(L, -1));
AppendMenuW(sm, MF_STRING, id, text.data());
Item mitem;
mitem.name = name;
mitem.itemName = item;
mitem.itemIndex = i;
items.push_back(mitem);
if (hascheck && !strcmp(item, current))
CheckMenuItem(sm, id, MF_CHECKED);
if (hascolor) {
lua_pushvalue(L, 6); // function
lua_pushnumber(L, i); // index
lua_pushvalue(L, -4); // name
luacall(L, 2, 3); // function returns red, green, blue
double red = luaL_checknumber(L, -3);
double green = luaL_checknumber(L, -2);
double blue = luaL_checknumber(L, -1);
lua_pop(L, 3); // pop result
HBITMAP bits = colorIcon(red, green, blue);
bitmaps.push_back(bits);
SetMenuItemBitmaps(sm, id, MF_BYCOMMAND, bits, bits);
}
lua_pop(L, 2); // item, text
}
AppendMenuW(hMenu, MF_STRING | MF_POPUP, (UINT_PTR) sm, title.data());
}
return 0;
}
// --------------------------------------------------------------------
static int menu_constructor(lua_State *L)
{
HWND hwnd = check_winid(L, 1);
Menu **m = (Menu **) lua_newuserdata(L, sizeof(Menu *));
*m = nullptr;
luaL_getmetatable(L, "Ipe.menu");
lua_setmetatable(L, -2);
*m = new PMenu(hwnd);
return 1;
}
// --------------------------------------------------------------------
class PTimer : public Timer {
public:
PTimer(lua_State *L0, int lua_object, const char *method);
virtual ~PTimer();
virtual int setInterval(lua_State *L);
virtual int active(lua_State *L);
virtual int start(lua_State *L);
virtual int stop(lua_State *L);
protected:
void elapsed();
static void CALLBACK TimerProc(HWND hwnd, UINT uMsg,
UINT_PTR idEvent, DWORD dwTime);
static std::vector all_timers;
protected:
UINT_PTR iTimer;
UINT iInterval;
};
std::vector PTimer::all_timers;
void CALLBACK PTimer::TimerProc(HWND, UINT, UINT_PTR id, DWORD)
{
for (int i = 0; i < int(all_timers.size()); ++i) {
if (id == all_timers[i]->iTimer) {
all_timers[i]->elapsed();
return;
}
}
}
PTimer::PTimer(lua_State *L0, int lua_object, const char *method)
: Timer(L0, lua_object, method)
{
iTimer = 0;
iInterval = 0;
all_timers.push_back(this);
}
PTimer::~PTimer()
{
if (iTimer)
KillTimer(nullptr, iTimer);
// remove it from all_timers
for (int i = 0; i < int(all_timers.size()); ++i) {
if (all_timers[i] == this) {
all_timers.erase(all_timers.begin() + i);
return;
}
}
}
void PTimer::elapsed()
{
callLua();
if (iSingleShot) {
KillTimer(nullptr, iTimer);
iTimer = 0;
}
}
int PTimer::setInterval(lua_State *L)
{
int t = (int)luaL_checkinteger(L, 2);
iInterval = t;
if (iTimer)
SetTimer(nullptr, iTimer, iInterval, TimerProc);
return 0;
}
int PTimer::active(lua_State *L)
{
lua_pushboolean(L, (iTimer != 0));
return 1;
}
int PTimer::start(lua_State *L)
{
if (iTimer == 0)
iTimer = SetTimer(nullptr, 0, iInterval, TimerProc);
return 0;
}
int PTimer::stop(lua_State *L)
{
if (iTimer) {
KillTimer(nullptr, iTimer);
iTimer = 0;
}
return 0;
}
// --------------------------------------------------------------------
static int timer_constructor(lua_State *L)
{
luaL_argcheck(L, lua_istable(L, 1), 1, "argument is not a table");
const char *method = luaL_checklstring(L, 2, nullptr);
Timer **t = (Timer **) lua_newuserdata(L, sizeof(Timer *));
*t = nullptr;
luaL_getmetatable(L, "Ipe.timer");
lua_setmetatable(L, -2);
// create a table with weak reference to Lua object
lua_createtable(L, 1, 1);
lua_pushliteral(L, "v");
lua_setfield(L, -2, "__mode");
lua_pushvalue(L, -1);
lua_setmetatable(L, -2);
lua_pushvalue(L, 1);
lua_rawseti(L, -2, 1);
int lua_object = luaL_ref(L, LUA_REGISTRYINDEX);
*t = new PTimer(L, lua_object, method);
return 1;
}
// --------------------------------------------------------------------
static COLORREF custom[16] = {
0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff,
0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff,
0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff,
0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff };
static int ipeui_getColor(lua_State *L)
{
HWND hwnd = check_winid(L, 1);
// const char *title = luaL_checkstring(L, 2);
double r = luaL_checknumber(L, 3);
double g = luaL_checknumber(L, 4);
double b = luaL_checknumber(L, 5);
CHOOSECOLOR cc;
ZeroMemory(&cc, sizeof(cc));
cc.lStructSize = sizeof(cc);
cc.hwndOwner = hwnd;
cc.Flags = CC_FULLOPEN | CC_RGBINIT;
cc.rgbResult = RGB(int(r * 255), int(g * 255), int(b * 255));
cc.lpCustColors = custom;
if (ChooseColor(&cc)) {
lua_pushnumber(L, GetRValue(cc.rgbResult) / 255.0);
lua_pushnumber(L, GetGValue(cc.rgbResult) / 255.0);
lua_pushnumber(L, GetBValue(cc.rgbResult) / 255.0);
return 3;
} else
return 0;
}
// --------------------------------------------------------------------
static int ipeui_fileDialog(lua_State *L)
{
static const char * const typenames[] = { "open", "save", nullptr };
HWND hwnd = check_winid(L, 1);
int type = luaL_checkoption(L, 2, nullptr, typenames);
WString caption(luaL_checklstring(L, 3, nullptr));
if (!lua_istable(L, 4))
luaL_argerror(L, 4, "table expected for filters");
std::wstring filters;
int nFilters = lua_rawlen(L, 4);
for (int i = 1; i <= nFilters; ++i) {
lua_rawgeti(L, 4, i);
luaL_argcheck(L, lua_isstring(L, -1), 4, "filter entry is not a string");
WString el(tostring(L, -1));
filters += el;
lua_pop(L, 1); // element i
}
filters.push_back(0); // terminate list
const char *dir = nullptr;
if (!lua_isnoneornil(L, 5))
dir = luaL_checklstring(L, 5, nullptr);
const char *name = nullptr;
if (!lua_isnoneornil(L, 6))
name = luaL_checklstring(L, 6, nullptr);
int selected = 0;
if (!lua_isnoneornil(L, 7))
selected = luaL_checkinteger(L, 7);
OPENFILENAMEW ofn;
wchar_t szFileName[MAX_PATH] = L"";
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = &filters[0];
ofn.nFilterIndex = selected;
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrDefExt = L"ipe";
if (name) {
WString wname(name);
wcsncpy(szFileName, wname.data(), MAX_PATH);
}
if (dir) {
WString wdir(dir);
ofn.lpstrInitialDir = wdir.data();
}
ofn.lpstrTitle = caption.data();
BOOL result;
if (type == 0) {
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
result = GetOpenFileNameW(&ofn);
} else {
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
result = GetSaveFileNameW(&ofn);
}
if (!result)
return 0;
std::string s = wideToUtf8(ofn.lpstrFile);
lua_pushstring(L, s.c_str());
lua_pushinteger(L, ofn.nFilterIndex);
return 2;
}
// --------------------------------------------------------------------
static int ipeui_messageBox(lua_State *L)
{
static const char * const options[] = {
"none", "warning", "information", "question", "critical", nullptr };
static const char * const buttontype[] = {
"ok", "okcancel", "yesnocancel", "discardcancel",
"savediscardcancel", nullptr };
HWND hwnd = check_winid(L, 1);
int type = luaL_checkoption(L, 2, "none", options);
const char *text = luaL_checklstring(L, 3, nullptr);
const char *details = nullptr;
if (!lua_isnoneornil(L, 4))
details = luaL_checklstring(L, 4, nullptr);
int buttons = 0;
if (lua_isnumber(L, 5))
buttons = (int)luaL_checkinteger(L, 5);
else if (!lua_isnoneornil(L, 5))
buttons = luaL_checkoption(L, 5, nullptr, buttontype);
UINT uType = MB_APPLMODAL;
switch (type) {
case 0:
default:
break;
case 1:
uType |= MB_ICONWARNING;
break;
case 2:
uType |= MB_ICONINFORMATION;
break;
case 3:
uType |= MB_ICONQUESTION;
break;
case 4:
uType |= MB_ICONERROR;
break;
}
switch (buttons) {
case 0:
default:
uType |= MB_OK;
break;
case 1:
uType |= MB_OKCANCEL;
break;
case 2:
uType |= MB_YESNOCANCEL;
break;
case 3:
// should be Discard Cancel
uType |= MB_OKCANCEL;
break;
case 4:
// should be Save Discard Cancel
uType |= MB_YESNOCANCEL;
break;
}
int ret = -1;
if (details) {
char buf[strlen(text) + strlen(details) + 8];
sprintf(buf, "%s\n\n%s", text, details);
WString wbuf(buf);
ret = MessageBoxW(hwnd, wbuf.data(), L"Ipe", uType);
} else
ret = MessageBoxW(hwnd, WString(text).data(), L"Ipe", uType);
switch (ret) {
case IDOK:
case IDYES:
lua_pushnumber(L, 1);
break;
case IDNO:
case IDIGNORE:
lua_pushnumber(L, 0);
break;
case IDCANCEL:
default:
lua_pushnumber(L, -1);
break;
}
return 1;
}
// --------------------------------------------------------------------
BOOL CALLBACK waitDialogProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message) {
case WM_INITDIALOG: {
HWND *p = (HWND *) lParam;
*p = hwnd;
return TRUE; }
default:
return FALSE;
}
}
VOID CALLBACK waitCallback(PVOID lpParameter, BOOLEAN timerOrWaitFired)
{
HWND *hDialog = (HWND *) lpParameter;
if (*hDialog)
EndDialog(*hDialog, 1);
}
static int ipeui_wait(lua_State *L)
{
Dialog **dlg = check_dialog(L, 1);
HWND parent = (*dlg)->winId();
std::vector t;
// Dialog flags
buildFlags(t, WS_POPUP | WS_BORDER | DS_SHELLFONT |
WS_SYSMENU | DS_MODALFRAME | WS_CAPTION);
t.push_back(1);
t.push_back(0); // offset of popup-window from parent window
t.push_back(0);
t.push_back(240);
t.push_back(60);
// menu
t.push_back(0);
// class
t.push_back(0);
// title
buildString(t, "Ipe: waiting");
// for DS_SHELLFONT
t.push_back(10);
buildString(t,"MS Shell Dlg");
if (t.size() % 2 != 0)
t.push_back(0);
buildFlags(t, WS_CHILD|WS_VISIBLE|SS_LEFT);
t.push_back(40);
t.push_back(20);
t.push_back(120);
t.push_back(20);
t.push_back(IDBASE);
buildControl(t, 0x0082, "Waiting for external editor");
// Declare and initialize process blocks
PROCESS_INFORMATION processInformation;
STARTUPINFOW startupInfo;
memset(&processInformation, 0, sizeof(processInformation));
memset(&startupInfo, 0, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);
// Call the executable program
const char *cmd = luaL_checklstring(L, 2, nullptr);
WString wcmd(cmd);
int result = CreateProcessW(nullptr, wcmd.data(), nullptr, nullptr, FALSE,
NORMAL_PRIORITY_CLASS|CREATE_NO_WINDOW,
nullptr, nullptr, &startupInfo, &processInformation);
if (result == 0)
return 0;
HWND dialogHandle = nullptr;
HANDLE waitHandle;
RegisterWaitForSingleObject(&waitHandle,
processInformation.hProcess,
waitCallback,
&dialogHandle,
INFINITE,
WT_EXECUTEINWAITTHREAD|WT_EXECUTEONLYONCE);
DialogBoxIndirectParamW((HINSTANCE) GetWindowLongPtr(parent, GWLP_HINSTANCE),
(LPCDLGTEMPLATE) &t[0],
parent, (DLGPROC) waitDialogProc,
(LPARAM) &dialogHandle);
UnregisterWait(waitHandle);
CloseHandle(processInformation.hProcess);
CloseHandle(processInformation.hThread);
return 0;
}
// --------------------------------------------------------------------
static int ipeui_currentDateTime(lua_State *L)
{
SYSTEMTIME st;
GetLocalTime(&st);
char buf[32];
sprintf(buf, "%04d%02d%02d%02d%02d%02d",
st.wYear, st.wMonth, st.wDay,
st.wHour, st.wMinute, st.wSecond);
lua_pushstring(L, buf);
return 1;
}
static int ipeui_startBrowser(lua_State *L)
{
const char *url = luaL_checklstring(L, 1, nullptr);
long long int res = (long long int) ShellExecuteA(nullptr, "open", url, nullptr, nullptr, SW_SHOWNORMAL);
lua_pushboolean(L, (res >= 32));
return 1;
}
// --------------------------------------------------------------------
static const struct luaL_Reg ipeui_functions[] = {
{ "Dialog", dialog_constructor },
{ "Menu", menu_constructor },
{ "Timer", timer_constructor },
{ "getColor", ipeui_getColor },
{ "fileDialog", ipeui_fileDialog },
{ "messageBox", ipeui_messageBox },
{ "waitDialog", ipeui_wait },
{ "currentDateTime", ipeui_currentDateTime },
{ "startBrowser", ipeui_startBrowser },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
int luaopen_ipeui(lua_State *L)
{
luaL_newlib(L, ipeui_functions);
lua_setglobal(L, "ipeui");
luaopen_ipeui_common(L);
return 0;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipeui/ipeui_wstring.h 0000644 0001750 0001750 00000003527 13561570220 017206 0 ustar otfried otfried // --------------------------------------------------------------------
// Helper class for Win32 Unicode interface
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef IPEUI_WSTRING_H
#define IPEUI_WSTRING_H
class WString : public std::wstring {
public:
#ifdef IPEBASE_H
WString(const ipe::String &s) : std::wstring( std::move(s.w()) ) { /* nothing */ }
#endif
WString(const std::string &s) { init(s.data(), s.size()); }
WString(const char *s) { init(s, -1); }
private:
void init(const char *s, int len);
};
extern BOOL setWindowText(HWND h, const char *s);
extern void sendMessage(HWND h, UINT code, const char *t, WPARAM wParam = 0);
// --------------------------------------------------------------------
#endif
ipe-7.2.13/src/ipeui/ipeui_gtk.cpp 0000644 0001750 0001750 00000064130 13561570220 016626 0 ustar otfried otfried // --------------------------------------------------------------------
// Lua bindings for GTK dialogs
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipeui_common.h"
// --------------------------------------------------------------------
class PDialog : public Dialog {
public:
PDialog(lua_State *L0, WINID parent, const char *caption);
virtual ~PDialog();
virtual void setMapped(lua_State *L, int idx);
virtual bool buildAndRun(int w, int h);
virtual void retrieveValues();
virtual void enableItem(int idx, bool value);
virtual void acceptDialog(lua_State *L);
private:
static void setListBoxRow(GtkTreeView *w, int row);
static GtkWidget *createListBox(const SElement &m);
static void fillListStore(GtkListStore *store, const SElement &m);
static void itemResponse(GtkWidget *item, PDialog *dlg);
private:
std::vector iWidgets;
};
PDialog::PDialog(lua_State *L0, WINID parent, const char *caption)
: Dialog(L0, parent, caption)
{
//
}
PDialog::~PDialog()
{
//
}
void PDialog::acceptDialog(lua_State *L)
{
int accept = lua_toboolean(L, 2);
(void) accept; // TODO
// QDialog::done(accept);
}
void PDialog::itemResponse(GtkWidget *item, PDialog *dlg)
{
for (int i = 0; i < int(dlg->iWidgets.size()); ++i) {
if (dlg->iWidgets[i] == item) {
dlg->callLua(dlg->iElements[i].lua_method);
return;
}
}
}
void PDialog::setMapped(lua_State *L, int idx)
{
SElement &m = iElements[idx];
GtkWidget *w = iWidgets[idx];
switch (m.type) {
case ELabel:
gtk_label_set_text(GTK_LABEL(w), m.text.z());
break;
case ECheckBox:
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), m.value);
break;
case ETextEdit:
gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(w)),
m.text.z(), -1);
break;
case EInput:
gtk_entry_set_text(GTK_ENTRY(w), m.text.z());
break;
case EList:
if (lua_istable(L, 3)) {
GtkTreeModel *mod = gtk_tree_view_get_model(GTK_TREE_VIEW(w));
fillListStore(GTK_LIST_STORE(mod), m);
}
setListBoxRow(GTK_TREE_VIEW(w), m.value);
break;
case ECombo:
if (lua_istable(L, 3)) {
GtkTreeModel *mod = gtk_combo_box_get_model(GTK_COMBO_BOX(w));
fillListStore(GTK_LIST_STORE(mod), m);
}
gtk_combo_box_set_active(GTK_COMBO_BOX(w), m.value);
break;
default:
break; // EButton
}
}
static String getTextEdit(GtkWidget *w)
{
GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(w));
GtkTextIter start;
GtkTextIter end;
gtk_text_buffer_get_iter_at_offset(buffer, &start, 0);
gtk_text_buffer_get_iter_at_offset(buffer, &end, -1);
gchar *s = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
return String(s);
}
void PDialog::retrieveValues()
{
for (int i = 0; i < int(iElements.size()); ++i) {
SElement &m = iElements[i];
GtkWidget *w = iWidgets[i];
switch (m.type) {
case EInput:
m.text = String(gtk_entry_get_text(GTK_ENTRY(w)));
break;
case ETextEdit:
m.text = getTextEdit(w);
break;
case EList:
{
GtkTreeSelection *s = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
GtkTreeModel *model;
GtkTreeIter iter;
if (gtk_tree_selection_get_selected(s, &model, &iter)) {
gint *path =
gtk_tree_path_get_indices(gtk_tree_model_get_path(model, &iter));
m.value = path[0];
} else
m.value = 0;
}
break;
case ECombo:
m.value = gtk_combo_box_get_active(GTK_COMBO_BOX(w));
break;
case ECheckBox:
m.value = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w));
break;
default:
break; // label and button - nothing to do
}
}
}
void PDialog::enableItem(int idx, bool value)
{
gtk_widget_set_sensitive(iWidgets[idx], value);
}
void PDialog::setListBoxRow(GtkTreeView *w, int row)
{
GtkTreeSelection *s = gtk_tree_view_get_selection(w);
GtkTreePath *path = gtk_tree_path_new_from_indices(row, -1);
gtk_tree_selection_select_path(s, path);
gtk_tree_path_free(path);
}
void PDialog::fillListStore(GtkListStore *store, const SElement &m)
{
GtkTreeIter iter;
gtk_list_store_clear(store);
for (int k = 0; k < int(m.items.size()); ++k) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, 0, m.items[k].z(), -1);
}
}
GtkWidget *PDialog::createListBox(const SElement &m)
{
GtkListStore *store = gtk_list_store_new(1, G_TYPE_STRING);
fillListStore(store, m);
GtkWidget *w = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
g_object_unref(G_OBJECT(store));
GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
// g_object_set(G_OBJECT(renderer), "foreground", "red", NULL);
GtkTreeViewColumn *column =
gtk_tree_view_column_new_with_attributes("Title", renderer,
"text", 0, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(w), column);
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(w), false);
GtkTreeSelection *s = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
gtk_tree_selection_set_mode(s, GTK_SELECTION_SINGLE);
setListBoxRow(GTK_TREE_VIEW(w), m.value);
return w;
}
static GtkWidget *addScrollBar(GtkWidget *w)
{
GtkWidget *ww = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(ww),
GTK_SHADOW_ETCHED_IN);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ww),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_container_add(GTK_CONTAINER(ww), w);
return ww;
}
static void ctrlEnterResponse(GtkWidget *, GtkDialog *dlg)
{
gtk_dialog_response(dlg, GTK_RESPONSE_ACCEPT);
}
static void escapeResponse(GtkWidget *, GtkDialog *dlg)
{
// catching escape, doing nothing
}
bool PDialog::buildAndRun(int w, int h)
{
hDialog = gtk_dialog_new();
gtk_window_set_title(GTK_WINDOW(hDialog), iCaption.z());
GtkAccelGroup *accel_group = gtk_accel_group_new();
gtk_window_add_accel_group(GTK_WINDOW(hDialog), accel_group);
guint accel_key;
GdkModifierType accel_mods;
gtk_accelerator_parse("Return", &accel_key, &accel_mods);
gtk_accel_group_connect(accel_group, accel_key, accel_mods, GtkAccelFlags(0),
g_cclosure_new(G_CALLBACK(ctrlEnterResponse),
hDialog, NULL));
if (iIgnoreEscape) {
gtk_accelerator_parse("Escape", &accel_key, &accel_mods);
gtk_accel_group_connect(accel_group, accel_key, accel_mods,
GtkAccelFlags(0),
g_cclosure_new(G_CALLBACK(escapeResponse),
hDialog, NULL));
}
if (w > 0 && h > 0)
gtk_window_set_default_size(GTK_WINDOW(hDialog), w, h);
GtkWidget *ca = gtk_dialog_get_content_area(GTK_DIALOG(hDialog));
GtkWidget *grid = gtk_table_new(iNoRows, iNoCols, FALSE);
gtk_table_set_row_spacings(GTK_TABLE(grid), 8);
gtk_table_set_col_spacings(GTK_TABLE(grid), 8);
gtk_table_set_homogeneous(GTK_TABLE(grid), FALSE);
gtk_container_set_border_width(GTK_CONTAINER(grid), 12);
gtk_box_pack_start(GTK_BOX(ca), grid, TRUE, TRUE, 0);
gtk_widget_show(grid);
GtkWidget *aa = gtk_dialog_get_action_area(GTK_DIALOG(hDialog));
for (int i = 0; i < int(iElements.size()); ++i) {
SElement &m = iElements[i];
GtkWidget *w = 0;
GtkWidget *ww = 0; // for alignment
int xOptions = 0; // GTK_EXPAND|GTK_SHRINK|GTK_FILL
int yOptions = 0; // GTK_EXPAND|GTK_SHRINK|GTK_FILL
if (m.row < 0) {
if (m.flags & EAccept) {
w = gtk_dialog_add_button(GTK_DIALOG(hDialog), m.text.z(),
GTK_RESPONSE_ACCEPT);
gtk_widget_set_can_default(w, TRUE);
gtk_widget_grab_default(w);
} else if (m.flags & EReject)
w = gtk_dialog_add_button(GTK_DIALOG(hDialog), m.text.z(),
GTK_RESPONSE_ACCEPT);
else {
w = gtk_button_new_with_label(m.text.z());
gtk_box_pack_start(GTK_BOX(aa), w, FALSE, FALSE, 0);
gtk_widget_show(w);
if (m.lua_method)
g_signal_connect(w, "clicked", G_CALLBACK(itemResponse), this);
}
} else {
switch (m.type) {
case ELabel:
ww = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
w = gtk_label_new(m.text.z());
gtk_container_add(GTK_CONTAINER(ww), w);
xOptions |= GTK_FILL; // left align in cell
break;
case EButton:
w = gtk_button_new_with_label(m.text.z());
break;
case ECheckBox:
w = gtk_check_button_new_with_label(m.text.z());
if (m.lua_method)
g_signal_connect(w, "toggled", G_CALLBACK(itemResponse), this);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), m.value);
break;
case EInput:
w = gtk_entry_new();
gtk_entry_set_activates_default(GTK_ENTRY(w), TRUE);
xOptions |= GTK_FILL;
break;
case ETextEdit:
w = gtk_text_view_new();
gtk_text_view_set_editable(GTK_TEXT_VIEW(w), !(m.flags & EReadOnly));
gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(w)),
m.text.z(), -1);
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(w), GTK_WRAP_WORD);
ww = addScrollBar(w);
xOptions |= GTK_FILL;
yOptions |= GTK_FILL;
break;
case ECombo:
{
ww = gtk_alignment_new(0.5, 0.0, 1.0, 0.0);
GtkListStore *store = gtk_list_store_new(1, G_TYPE_STRING);
fillListStore(store, m);
w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), renderer, TRUE);
gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(w), renderer,
"text", 0);
gtk_container_add(GTK_CONTAINER(ww), w);
gtk_combo_box_set_active(GTK_COMBO_BOX(w), m.value);
xOptions |= GTK_FILL;
yOptions |= GTK_FILL; // align at top
}
break;
case EList:
w = createListBox(m);
ww = addScrollBar(w);
xOptions |= GTK_FILL;
yOptions |= GTK_FILL;
break;
default:
break;
}
for (int r = m.row; r < m.row + m.rowspan; ++r)
if (iRowStretch[r] > 0)
yOptions |= GTK_EXPAND;
for (int c = m.col; c < m.col + m.colspan; ++c)
if (iColStretch[c] > 0)
xOptions |= GTK_EXPAND;
if (ww == 0)
ww = w;
if (ww != 0) {
gtk_table_attach(GTK_TABLE(grid), ww,
m.col, m.col+m.colspan, m.row, m.row+m.rowspan,
GtkAttachOptions(xOptions),
GtkAttachOptions(yOptions), 0, 0);
gtk_widget_show(ww);
gtk_widget_show(w);
}
}
if (m.flags & EDisabled)
gtk_widget_set_sensitive(w, false);
iWidgets.push_back(w);
}
gint result = gtk_dialog_run(GTK_DIALOG(hDialog));
retrieveValues(); // for future reference
gtk_widget_destroy(hDialog);
hDialog = NULL;
return (result == GTK_RESPONSE_ACCEPT);
}
// --------------------------------------------------------------------
static int dialog_constructor(lua_State *L)
{
WINID parent = check_winid(L, 1);
const char *s = luaL_checkstring(L, 2);
Dialog **dlg = (Dialog **) lua_newuserdata(L, sizeof(Dialog *));
*dlg = 0;
luaL_getmetatable(L, "Ipe.dialog");
lua_setmetatable(L, -2);
*dlg = new PDialog(L, parent, s);
return 1;
}
// --------------------------------------------------------------------
class PMenu : public Menu {
public:
PMenu(WINID parent);
virtual ~PMenu();
virtual int add(lua_State *L);
virtual int execute(lua_State *L);
private:
static void itemResponse(GtkWidget *item, PMenu *menu);
static void deactivateResponse(GtkMenuShell *, PMenu *);
static void positionResponse(GtkMenu *, gint *x, gint *y,
gboolean *push_in, PMenu *menu);
private:
GtkWidget *iMenu;
struct Item {
gchar *name;
gchar *itemName;
int itemIndex;
GtkWidget *widget;
};
std::vector
- items;
int iSelectedItem;
int iPopupX;
int iPopupY;
};
void PMenu::itemResponse(GtkWidget *item, PMenu *menu)
{
for (int i = 0; i < int(menu->items.size()); ++i) {
if (menu->items[i].widget == item) {
menu->iSelectedItem = i;
return;
}
}
}
void PMenu::deactivateResponse(GtkMenuShell *, PMenu *)
{
gtk_main_quit(); // drop out of nested loop
}
// NOT USED: Better to just let GTK use the cursor position
void PMenu::positionResponse(GtkMenu *, gint *x, gint *y,
gboolean *push_in, PMenu *menu)
{
*x = menu->iPopupX;
*y = menu->iPopupY;
*push_in = TRUE;
}
PMenu::PMenu(WINID parent)
{
iMenu = gtk_menu_new();
g_object_ref_sink(iMenu);
g_signal_connect(iMenu, "deactivate",
G_CALLBACK(deactivateResponse), this);
}
PMenu::~PMenu()
{
for (int i = 0; i < int(items.size()); ++i) {
g_free(items[i].name);
g_free(items[i].itemName);
}
g_object_unref(iMenu);
}
int PMenu::execute(lua_State *L)
{
iPopupX = (int)luaL_checkinteger(L, 2);
iPopupY = (int)luaL_checkinteger(L, 3);
iSelectedItem = -1;
gtk_menu_popup(GTK_MENU(iMenu), NULL, NULL,
// GtkMenuPositionFunc(positionResponse), this,
NULL, NULL,
0, // initiated by button release
gtk_get_current_event_time());
// nested main loop
gtk_main();
if (0 <= iSelectedItem && iSelectedItem < int(items.size())) {
lua_pushstring(L, items[iSelectedItem].name);
lua_pushinteger(L, items[iSelectedItem].itemIndex);
if (items[iSelectedItem].itemName)
lua_pushstring(L, items[iSelectedItem].itemName);
else
lua_pushstring(L, "");
return 3;
} else
return 0;
}
static GtkWidget *colorIcon(double red, double green, double blue)
{
GtkWidget *w = gtk_drawing_area_new();
gtk_widget_set_size_request(w, 13, 13);
GdkColor color;
color.red = int(red * 65535.0);
color.green = int(green * 65535.0);
color.blue = int(blue * 65535.0);
gtk_widget_modify_bg(w, GTK_STATE_NORMAL, &color);
g_object_ref_sink(w);
return w;
}
int PMenu::add(lua_State *L)
{
const char *name = luaL_checkstring(L, 2);
const char *title = luaL_checkstring(L, 3);
if (lua_gettop(L) == 3) {
GtkWidget *w = gtk_menu_item_new_with_label(title);
gtk_menu_shell_append(GTK_MENU_SHELL(iMenu), w);
g_signal_connect(w, "activate", G_CALLBACK(itemResponse), this);
gtk_widget_show(w);
Item item;
item.name = g_strdup(name);
item.itemName = 0;
item.itemIndex = 0;
item.widget = w;
items.push_back(item);
} else {
luaL_argcheck(L, lua_istable(L, 4), 4, "argument is not a table");
bool hasmap = !lua_isnoneornil(L, 5) && lua_isfunction(L, 5);
bool hastable = !hasmap && !lua_isnoneornil(L, 5);
bool hascolor = !lua_isnoneornil(L, 6) && lua_isfunction(L, 6);
bool hascheck = !hascolor && !lua_isnoneornil(L, 6);
if (hastable)
luaL_argcheck(L, lua_istable(L, 5), 5,
"argument is not a function or table");
const char *current = 0;
if (hascheck) {
luaL_argcheck(L, lua_isstring(L, 6), 6,
"argument is not a function or string");
current = luaL_checkstring(L, 6);
}
GtkWidget *sm = gtk_menu_new();
int no = lua_rawlen(L, 4);
for (int i = 1; i <= no; ++i) {
lua_rawgeti(L, 4, i);
luaL_argcheck(L, lua_isstring(L, -1), 4, "items must be strings");
const char *item = lua_tostring(L, -1);
if (hastable) {
lua_rawgeti(L, 5, i);
luaL_argcheck(L, lua_isstring(L, -1), 5, "labels must be strings");
} else if (hasmap) {
lua_pushvalue(L, 5); // function
lua_pushnumber(L, i); // index
lua_pushvalue(L, -3); // name
lua_call(L, 2, 1); // function returns label
luaL_argcheck(L, lua_isstring(L, -1), 5,
"function does not return string");
} else
lua_pushvalue(L, -1);
const char *text = lua_tostring(L, -1);
GtkWidget *w = 0;
if (hascheck)
w = gtk_check_menu_item_new_with_label(text);
else if (hascolor)
w = gtk_image_menu_item_new_with_label(text);
else
w = gtk_menu_item_new_with_label(text);
if (hascheck && !g_strcmp0(item, current))
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w), true);
gtk_menu_shell_append(GTK_MENU_SHELL(sm), w);
g_signal_connect(w, "activate", G_CALLBACK(itemResponse), this);
gtk_widget_show(w);
Item mitem;
mitem.name = g_strdup(name);
mitem.itemName = g_strdup(item);
mitem.itemIndex = i;
mitem.widget = w;
items.push_back(mitem);
if (hascolor) {
gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(w),
true);
lua_pushvalue(L, 6); // function
lua_pushnumber(L, i); // index
lua_pushvalue(L, -4); // name
lua_call(L, 2, 3); // function returns red, green, blue
double red = luaL_checknumber(L, -3);
double green = luaL_checknumber(L, -2);
double blue = luaL_checknumber(L, -1);
lua_pop(L, 3); // pop result
GtkWidget *im = colorIcon(red, green, blue);
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(w), im);
g_object_unref(im);
}
lua_pop(L, 2); // item, text
}
GtkWidget *sme = gtk_menu_item_new_with_label(title);
gtk_widget_show(sme);
gtk_menu_item_set_submenu(GTK_MENU_ITEM(sme), sm);
gtk_menu_shell_append(GTK_MENU_SHELL(iMenu), sme);
gtk_widget_show(sme);
}
return 0;
}
// --------------------------------------------------------------------
static int menu_constructor(lua_State *L)
{
GtkWidget *parent = check_winid(L, 1);
Menu **m = (Menu **) lua_newuserdata(L, sizeof(Menu *));
*m = 0;
luaL_getmetatable(L, "Ipe.menu");
lua_setmetatable(L, -2);
*m = new PMenu(parent);
return 1;
}
// --------------------------------------------------------------------
static int ipeui_getColor(lua_State *L)
{
check_winid(L, 1);
const char *title = luaL_checkstring(L, 2);
double r = luaL_checknumber(L, 3);
double g = luaL_checknumber(L, 4);
double b = luaL_checknumber(L, 5);
GdkColor color;
color.red = int(r * 65535);
color.green = int(g * 65535);
color.blue = int(b * 65535);
GtkWidget *dlg = gtk_color_selection_dialog_new(title);
GtkColorSelection *sel =
GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection
(GTK_COLOR_SELECTION_DIALOG(dlg)));
gtk_color_selection_set_current_color(sel, &color);
int result = gtk_dialog_run(GTK_DIALOG(dlg));
if (result == GTK_RESPONSE_OK) {
gtk_color_selection_get_current_color(GTK_COLOR_SELECTION(sel), &color);
gtk_widget_destroy(dlg);
lua_pushnumber(L, color.red / 65535.0);
lua_pushnumber(L, color.green / 65535.0);
lua_pushnumber(L, color.blue / 65535.0);
return 3;
}
gtk_widget_destroy(dlg);
return 0;
}
// --------------------------------------------------------------------
static int ipeui_fileDialog(lua_State *L)
{
static const char * const typenames[] = { "open", "save", 0 };
GtkWindow *parent = GTK_WINDOW(check_winid(L, 1));
int type = luaL_checkoption(L, 2, 0, typenames);
const char *caption = luaL_checkstring(L, 3);
// GTK dialog uses no filters: args 4 and 7 are not used
const char *dir = 0;
if (!lua_isnoneornil(L, 5))
dir = luaL_checkstring(L, 5);
const char *name = 0;
if (!lua_isnoneornil(L, 6))
name = luaL_checkstring(L, 6);
GtkWidget *dlg =
gtk_file_chooser_dialog_new(caption, parent,
(type ?
GTK_FILE_CHOOSER_ACTION_SAVE :
GTK_FILE_CHOOSER_ACTION_OPEN),
GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
NULL);
if (dir)
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dlg), dir);
if (name)
gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dlg), name);
int result = gtk_dialog_run(GTK_DIALOG(dlg));
if (result == GTK_RESPONSE_ACCEPT) {
char *fn = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg));
lua_pushstring(L, fn);
lua_pushinteger(L, 1); // name filters not used
g_free(fn);
gtk_widget_destroy(dlg);
return 2;
}
gtk_widget_destroy(dlg);
return 0;
}
// --------------------------------------------------------------------
static int ipeui_messageBox(lua_State *L)
{
static const char * const options[] = {
"none", "warning", "information", "question", "critical", 0 };
static const char * const buttontype[] = {
"ok", "okcancel", "yesnocancel", "discardcancel",
"savediscardcancel", 0 };
GtkWidget *parent = check_winid(L, 1);
int type = luaL_checkoption(L, 2, "none", options);
const char *text = luaL_checkstring(L, 3);
const char *details = 0;
if (!lua_isnoneornil(L, 4))
details = luaL_checkstring(L, 4);
int buttons = 0;
if (lua_isnumber(L, 5))
buttons = (int)luaL_checkinteger(L, 5);
else if (!lua_isnoneornil(L, 5))
buttons = luaL_checkoption(L, 5, 0, buttontype);
GtkMessageType t = GTK_MESSAGE_OTHER;
switch (type) {
case 0: t = GTK_MESSAGE_OTHER; break;
case 1: t = GTK_MESSAGE_WARNING; break;
case 2: t = GTK_MESSAGE_INFO; break;
case 3: t = GTK_MESSAGE_QUESTION; break;
case 4: t = GTK_MESSAGE_ERROR; break;
default: break;
}
GtkWidget *dlg =
gtk_message_dialog_new(GTK_WINDOW(parent), GTK_DIALOG_MODAL,
t, GTK_BUTTONS_NONE, "%s", text);
if (details)
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dlg),
"%s", details);
switch (buttons) {
case 0: // "ok"
gtk_dialog_add_buttons(GTK_DIALOG(dlg),
GTK_STOCK_OK, GTK_RESPONSE_OK,
NULL);
break;
case 1: // "okcancel"
gtk_dialog_add_buttons(GTK_DIALOG(dlg),
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OK, GTK_RESPONSE_OK,
NULL);
break;
case 2: // "yesnocancel"
gtk_dialog_add_buttons(GTK_DIALOG(dlg),
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_NO, GTK_RESPONSE_NO,
GTK_STOCK_YES, GTK_RESPONSE_YES,
NULL);
break;
case 3: // "discardcancel"
gtk_dialog_add_buttons(GTK_DIALOG(dlg),
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_DISCARD, GTK_RESPONSE_NO,
NULL);
break;
case 4: // "savediscardcancel"
gtk_dialog_add_buttons(GTK_DIALOG(dlg),
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_DISCARD, GTK_RESPONSE_NO,
GTK_STOCK_SAVE, GTK_RESPONSE_YES,
NULL);
default:
break;
}
int result = gtk_dialog_run(GTK_DIALOG(dlg));
switch (result) {
case GTK_RESPONSE_YES:
case GTK_RESPONSE_OK:
lua_pushnumber(L, 1);
break;
case GTK_RESPONSE_NO:
lua_pushnumber(L, 0);
break;
case GTK_RESPONSE_CANCEL:
default:
lua_pushnumber(L, -1);
break;
}
gtk_widget_destroy(dlg);
return 1;
}
// --------------------------------------------------------------------
class PTimer : public Timer {
public:
PTimer(lua_State *L0, int lua_object, const char *method);
virtual ~PTimer();
virtual int setInterval(lua_State *L);
virtual int active(lua_State *L);
virtual int start(lua_State *L);
virtual int stop(lua_State *L);
private:
gboolean elapsed();
static gboolean timerCallback(gpointer data);
private:
guint iTimer;
guint iInterval;
};
gboolean PTimer::timerCallback(gpointer data)
{
PTimer *t = (PTimer *) data;
return t->elapsed();
}
PTimer::PTimer(lua_State *L0, int lua_object, const char *method)
: Timer(L0, lua_object, method)
{
iTimer = 0;
iInterval = 0;
}
PTimer::~PTimer()
{
if (iTimer != 0)
g_source_remove(iTimer);
}
gboolean PTimer::elapsed()
{
callLua();
if (iSingleShot) {
iTimer = 0;
return FALSE;
} else
return TRUE;
}
// does not update interval on running timer
int PTimer::setInterval(lua_State *L)
{
int t = (int)luaL_checkinteger(L, 2);
iInterval = t;
return 0;
}
int PTimer::active(lua_State *L)
{
lua_pushboolean(L, (iTimer != 0));
return 1;
}
int PTimer::start(lua_State *L)
{
if (iTimer == 0) {
if (iInterval > 3000)
iTimer = g_timeout_add_seconds(iInterval / 1000,
GSourceFunc(timerCallback), this);
else
iTimer = g_timeout_add(iInterval, GSourceFunc(timerCallback), this);
}
return 0;
}
int PTimer::stop(lua_State *L)
{
if (iTimer != 0) {
g_source_remove(iTimer);
iTimer = 0;
}
return 0;
}
// --------------------------------------------------------------------
static int timer_constructor(lua_State *L)
{
luaL_argcheck(L, lua_istable(L, 1), 1, "argument is not a table");
const char *method = luaL_checkstring(L, 2);
Timer **t = (Timer **) lua_newuserdata(L, sizeof(Timer *));
*t = 0;
luaL_getmetatable(L, "Ipe.timer");
lua_setmetatable(L, -2);
// create a table with weak reference to Lua object
lua_createtable(L, 1, 1);
lua_pushliteral(L, "v");
lua_setfield(L, -2, "__mode");
lua_pushvalue(L, -1);
lua_setmetatable(L, -2);
lua_pushvalue(L, 1);
lua_rawseti(L, -2, 1);
int lua_object = luaL_ref(L, LUA_REGISTRYINDEX);
*t = new PTimer(L, lua_object, method);
return 1;
}
// --------------------------------------------------------------------
static int ipeui_wait(lua_State *L)
{
luaL_error(L, "'waitDialog' is not yet implemented.");
return 0;
}
// --------------------------------------------------------------------
static int ipeui_currentDateTime(lua_State *L)
{
time_t t = time(NULL);
struct tm *tmp = localtime(&t);
if (tmp == NULL)
return 0;
char buf[16];
sprintf(buf, "%04d%02d%02d%02d%02d%02d",
1900 + tmp->tm_year, 1 + tmp->tm_mon, tmp->tm_mday,
tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
lua_pushstring(L, buf);
return 1;
}
// --------------------------------------------------------------------
static const struct luaL_Reg ipeui_functions[] = {
{ "Dialog", dialog_constructor },
{ "Menu", menu_constructor },
{ "Timer", timer_constructor },
{ "getColor", ipeui_getColor },
{ "fileDialog", ipeui_fileDialog },
{ "messageBox", ipeui_messageBox },
{ "waitDialog", ipeui_wait },
{ "currentDateTime", ipeui_currentDateTime },
{ 0, 0},
};
// --------------------------------------------------------------------
int luaopen_ipeui(lua_State *L)
{
luaL_newlib(L, ipeui_functions);
lua_setglobal(L, "ipeui");
luaopen_ipeui_common(L);
return 0;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/config.mak 0000644 0001750 0001750 00000010050 13561570220 014756 0 ustar otfried otfried # -*- makefile -*-
# --------------------------------------------------------------------
#
# Ipe configuration for Unix
#
# *** This File is NOT USED on MAC OS X ***
#
# --------------------------------------------------------------------
# Optional component
# --------------------------------------------------------------------
#
# Compile support for online Latex translation?
#IPECURL = 1
#
# --------------------------------------------------------------------
# Include and linking options for libraries
# --------------------------------------------------------------------
#
# We just query "pkg-config" for the correct flags. If this doesn't
# work on your system, enter the correct linker flags and directories
# directly.
#
# The name of the Lua package (it could be "lua", "lua53", or "lua5.3")
#
LUA_PACKAGE ?= lua5.3
#
ZLIB_CFLAGS ?=
ZLIB_LIBS ?= -lz
JPEG_CFLAGS ?=
JPEG_LIBS ?= -ljpeg
PNG_CFLAGS ?= $(shell pkg-config --cflags libpng)
PNG_LIBS ?= $(shell pkg-config --libs libpng)
FREETYPE_CFLAGS ?= $(shell pkg-config --cflags freetype2)
FREETYPE_LIBS ?= $(shell pkg-config --libs freetype2)
CAIRO_CFLAGS ?= $(shell pkg-config --cflags cairo)
CAIRO_LIBS ?= $(shell pkg-config --libs cairo)
LUA_CFLAGS ?= $(shell pkg-config --cflags $(LUA_PACKAGE))
LUA_LIBS ?= $(shell pkg-config --libs $(LUA_PACKAGE))
QT_CFLAGS ?= $(shell pkg-config --cflags Qt5Gui Qt5Widgets Qt5Core)
QT_LIBS ?= $(shell pkg-config --libs Qt5Gui Qt5Widgets Qt5Core)
ifdef IPECURL
CURL_CFLAGS ?= $(shell pkg-config --cflags libcurl)
CURL_LIBS ?= $(shell pkg-config --libs libcurl)
endif
#
# Library needed to use dlopen/dlsym/dlclose calls
#
DL_LIBS ?= -ldl
#
# MOC is the Qt meta-object compiler.
# Make sure it's the right one for Qt5.
MOC ?= moc
#
# --------------------------------------------------------------------
#
# The C++ compiler
# I'm testing with g++ and clang++.
#
CXX = g++
#
# Special compilation flags for compiling shared libraries
# 64-bit Linux requires shared libraries to be compiled as
# position independent code, that is -fpic or -fPIC
# Qt5 seems to require -fPIC
DLL_CFLAGS = -fPIC
#
# --------------------------------------------------------------------
#
# Installing Ipe:
#
IPEVERS = 7.2.13
#
# IPEPREFIX is the global prefix for the Ipe directory structure, which
# you can override individually for any of the specific directories.
# You could choose "/usr/local" or "/opt/ipe7", or
# even "/usr", or "$(HOME)/ipe7" if you have to install in your home
# directory.
#
# If you are installing Ipe in a networked environment, keep in mind
# that executables, ipelets, and Ipe library are machine-dependent,
# while the documentation and fonts can be shared.
#
#IPEPREFIX := /usr/local
#IPEPREFIX := /usr
#IPEPREFIX := /opt/ipe7
#
ifeq "$(IPEPREFIX)" ""
$(error You need to specify IPEPREFIX!)
endif
#
# Where Ipe executables will be installed ('ipe', 'ipetoipe' etc)
IPEBINDIR ?= $(IPEPREFIX)/bin
#
# Where the Ipe libraries will be installed ('libipe.so' etc.)
IPELIBDIR ?= $(IPEPREFIX)/lib
#
# Where the header files for Ipelib will be installed:
IPEHEADERDIR ?= $(IPEPREFIX)/include
#
# Where Ipelets will be installed:
IPELETDIR ?= $(IPEPREFIX)/lib/ipe/$(IPEVERS)/ipelets
#
# Where Lua code will be installed
# (This is the part of the Ipe program written in the Lua language)
IPELUADIR ?= $(IPEPREFIX)/share/ipe/$(IPEVERS)/lua
#
# Directory where Ipe will look for scripts
# (standard scripts will also be installed here)
IPESCRIPTDIR ?= $(IPEPREFIX)/share/ipe/$(IPEVERS)/scripts
#
# Directory where Ipe will look for style files
# (standard Ipe styles will also be installed here)
IPESTYLEDIR ?= $(IPEPREFIX)/share/ipe/$(IPEVERS)/styles
#
# IPEICONDIR contains the icons used in the Ipe user interface
#
IPEICONDIR ?= $(IPEPREFIX)/share/ipe/$(IPEVERS)/icons
#
# IPEDOCDIR contains the Ipe documentation (mostly html files)
#
IPEDOCDIR ?= $(IPEPREFIX)/share/ipe/$(IPEVERS)/doc
#
# The Ipe manual pages are installed into IPEMANDIR
#
IPEMANDIR ?= $(IPEPREFIX)/share/man/man1
#
# --------------------------------------------------------------------
ipe-7.2.13/src/ipelua/ 0000755 0001750 0001750 00000000000 13561570220 014302 5 ustar otfried otfried ipe-7.2.13/src/ipelua/ipelib.cpp 0000644 0001750 0001750 00000047170 13561570220 016263 0 ustar otfried otfried // --------------------------------------------------------------------
// ipelib.cpp
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipelua.h"
#include "ipedoc.h"
#include "ipebitmap.h"
#include
#include
using namespace ipe;
using namespace ipelua;
// --------------------------------------------------------------------
static const char * const format_name[] =
{ "xml", "pdf", "eps", "ipe5", "unknown" };
void ipelua::make_metatable(lua_State *L, const char *name,
const struct luaL_Reg *methods)
{
luaL_newmetatable(L, name);
lua_pushstring(L, "__index");
lua_pushvalue(L, -2); /* pushes the metatable */
lua_settable(L, -3); /* metatable.__index = metatable */
luaL_setfuncs(L, methods, 0);
lua_pop(L, 1);
}
bool ipelua::is_type(lua_State *L, int ud, const char *tname)
{
if (lua_isuserdata(L, ud) && lua_getmetatable(L, ud)) {
lua_getfield(L, LUA_REGISTRYINDEX, tname);
if (lua_rawequal(L, -1, -2)) {
lua_pop(L, 2);
return true;
}
}
return false;
}
String ipelua::check_filename(lua_State *L, int index)
{
return String(luaL_checklstring(L, index, nullptr));
}
// --------------------------------------------------------------------
// Document
// --------------------------------------------------------------------
static int document_constructor(lua_State *L)
{
bool has_fname = (lua_gettop(L) > 0);
Document **d = (Document **) lua_newuserdata(L, sizeof(Document *));
*d = nullptr;
luaL_getmetatable(L, "Ipe.document");
lua_setmetatable(L, -2);
// should we load a document?
if (has_fname) {
String fname = check_filename(L, 1);
int reason;
*d = Document::load(fname.z(), reason);
if (*d)
return 1;
lua_pop(L, 1); // pop empty document udata
lua_pushnil(L); // return nil and ...
switch (reason) {
case Document::EVersionTooOld:
lua_pushliteral(L, "The Ipe version of this document is too old");
break;
case Document::EVersionTooRecent:
lua_pushliteral(L, "The document was created by a newer version of Ipe");
break;
case Document::EFileOpenError:
lua_pushfstring(L, "Error opening file: %s", strerror(errno));
break;
case Document::ENotAnIpeFile:
lua_pushliteral(L, "The document was not created by Ipe");
break;
default:
lua_pushfstring(L, "Parsing error at position %d", reason);
break;
}
lua_pushnumber(L, reason);
return 3;
} else {
// create new empty document
*d = new Document();
// create the first page
(*d)->insert(0, Page::basic());
return 1;
}
}
static int document_destruct(lua_State *L)
{
Document **d = check_document(L, 1);
delete (*d);
*d = nullptr;
return 0;
}
static int document_tostring(lua_State *L)
{
check_document(L, 1);
lua_pushfstring(L, "Document@%p", lua_topointer(L, 1));
return 1;
}
// --------------------------------------------------------------------
static int check_pageno(lua_State *L, int i, Document *d, int extra = 0)
{
int n = (int)luaL_checkinteger(L, i);
luaL_argcheck(L, 1 <= n && n <= d->countPages() + extra, i,
"invalid page number");
return n - 1;
}
static int document_index(lua_State *L)
{
Document **d = check_document(L, 1);
if (lua_type(L, 2) == LUA_TNUMBER) {
int n = check_pageno(L, 2, *d);
push_page(L, (*d)->page(n), false);
} else {
const char *key = luaL_checklstring(L, 2, nullptr);
if (!luaL_getmetafield(L, 1, key))
lua_pushnil(L);
}
return 1;
}
// Document --> int
static int document_len(lua_State *L)
{
Document **d = check_document(L, 1);
lua_pushinteger(L, (*d)->countPages());
return 1;
}
// arguments: document, counter
static int document_page_iterator(lua_State *L)
{
Document **d = check_document(L, 1);
int i = luaL_checkinteger(L, 2);
i = i + 1;
if (i <= (*d)->countPages()) {
lua_pushinteger(L, i); // new counter
push_page(L, (*d)->page(i-1), false); // page
return 2;
} else
return 0;
}
// returns page iterator for use in for loop
// returns iterator function, invariant state, control variable
static int document_pages(lua_State *L)
{
(void) check_document(L, 1);
lua_pushcfunction(L, document_page_iterator); // iterator function
lua_pushvalue(L, 1); // document
lua_pushinteger(L, 0); // counter
return 3;
}
// "export", "nozip", "markedview"
static uint32_t check_flags(lua_State *L, int index)
{
if (lua_isnoneornil(L, index))
return 0;
luaL_argcheck(L, lua_istable(L, index), index, "argument is not a table");
uint32_t flags = 0;
lua_getfield(L, index, "export");
if (lua_toboolean(L, -1))
flags |= SaveFlag::Export;
lua_pop(L, 1);
lua_getfield(L, index, "nozip");
if (lua_toboolean(L, -1))
flags |= SaveFlag::NoZip;
lua_pop(L, 1);
lua_getfield(L, index, "keepnotes");
if (lua_toboolean(L, -1))
flags |= SaveFlag::KeepNotes;
lua_pop(L, 1);
lua_getfield(L, index, "markedview");
if (lua_toboolean(L, -1))
flags |= SaveFlag::MarkedView;
lua_pop(L, 1);
return flags;
}
static int document_save(lua_State *L)
{
Document **d = check_document(L, 1);
String fname = check_filename(L, 2);
FileFormat format;
if (lua_isnoneornil(L, 3))
format = Document::formatFromFilename(fname);
else
format = FileFormat(luaL_checkoption(L, 3, nullptr, format_name));
uint32_t flags = check_flags(L, 4);
bool result = (*d)->save(fname.z(), format, flags);
lua_pushboolean(L, result);
return 1;
}
static int document_exportPages(lua_State *L)
{
Document **d = check_document(L, 1);
String fname = check_filename(L, 2);
uint32_t flags = check_flags(L, 3);
int fromPage = check_pageno(L, 4, *d);
int toPage = check_pageno(L, 5, *d);
bool result = (*d)->exportPages(fname.z(), flags, fromPage, toPage);
lua_pushboolean(L, result);
return 1;
}
static int document_exportView(lua_State *L)
{
Document **d = check_document(L, 1);
String fname = check_filename(L, 2);
FileFormat format;
if (lua_isnoneornil(L, 3))
format = Document::formatFromFilename(fname);
else
format = FileFormat(luaL_checkoption(L, 3, nullptr, format_name));
uint32_t flags = check_flags(L, 4);
int pno = check_pageno(L, 5, *d);
int vno = check_viewno(L, 6, (*d)->page(pno));
bool result = (*d)->exportView(fname.z(), format, flags, pno, vno);
lua_pushboolean(L, result);
return 1;
}
// Document --> int
static int document_countTotalViews(lua_State *L)
{
Document **d = check_document(L, 1);
lua_pushinteger(L, (*d)->countTotalViews());
return 1;
}
static int document_sheets(lua_State *L)
{
Document **d = check_document(L, 1);
push_cascade(L, (*d)->cascade(), false);
return 1;
}
static int document_replaceSheets(lua_State *L)
{
Document **d = check_document(L, 1);
SCascade *p = check_cascade(L, 2);
Cascade *sheets = p->cascade;
if (!p->owned)
sheets = new Cascade(*p->cascade);
Cascade *old = (*d)->replaceCascade(sheets);
p->owned = false; // now owned by document
push_cascade(L, old);
return 1;
}
static int document_runLatex(lua_State *L)
{
Document **d = check_document(L, 1);
String docname;
if (!lua_isnoneornil(L, 2))
docname = luaL_checklstring(L, 2, nullptr);
String log;
int result = (*d)->runLatex(docname, log);
if (result == Document::ErrNone) {
lua_pushboolean(L, true);
lua_pushnil(L);
lua_pushnil(L);
} else if (result == Document::ErrNoText) {
lua_pushboolean(L, true);
lua_pushnil(L);
lua_pushliteral(L, "notext");
} else {
lua_pushboolean(L, false);
switch (result) {
case Document::ErrNoDir:
lua_pushliteral(L, "Directory does not exist and cannot be created");
lua_pushliteral(L, "nodir");
break;
case Document::ErrWritingSource:
lua_pushliteral(L, "Error writing Latex source");
lua_pushliteral(L, "writingsource");
break;
case Document::ErrOldPdfLatex:
lua_pushliteral(L, "Your installed version of Pdflatex is too old");
lua_pushliteral(L, "oldpdflatex");
break;
case Document::ErrRunLatex:
lua_pushliteral(L, "There was an error trying to run Pdflatex");
lua_pushliteral(L, "runlatex");
break;
case Document::ErrLatex:
lua_pushliteral(L, "There were Latex errors");
lua_pushliteral(L, "latex");
break;
case Document::ErrLatexOutput:
lua_pushliteral(L, "There was an error reading the Pdflatex output");
lua_pushliteral(L, "latexoutput");
break;
}
}
push_string(L, log);
return 4;
}
static int document_checkStyle(lua_State *L)
{
Document **d = check_document(L, 1);
AttributeSeq seq;
(*d)->checkStyle(seq);
lua_createtable(L, 0, seq.size());
for (int i = 0; i < size(seq); ++i) {
push_attribute(L, seq[i]);
lua_rawseti(L, -2, i+1);
}
return 1;
}
static int document_set(lua_State *L)
{
Document **d = check_document(L, 1);
int no = check_pageno(L, 2, *d);
Page *p = check_page(L, 3)->page;
Page *old = (*d)->set(no, new Page(*p));
push_page(L, old);
return 1;
}
static int document_insert(lua_State *L)
{
Document **d = check_document(L, 1);
int no = check_pageno(L, 2, *d, 1);
SPage *p = check_page(L, 3);
(*d)->insert(no, new Page(*p->page));
return 0;
}
static int document_append(lua_State *L)
{
Document **d = check_document(L, 1);
SPage *p = check_page(L, 2);
(*d)->push_back(new Page(*p->page));
return 0;
}
static int document_remove(lua_State *L)
{
Document **d = check_document(L, 1);
int no = check_pageno(L, 2, *d);
Page *old = (*d)->remove(no);
push_page(L, old);
return 1;
}
static const char * const tex_engine_names[] = {
"default", "pdftex", "xetex", "luatex" };
static int document_properties(lua_State *L)
{
Document **d = check_document(L, 1);
Document::SProperties prop = (*d)->properties();
lua_createtable(L, 11, 0);
push_string(L, prop.iTitle);
lua_setfield(L, -2, "title");
push_string(L, prop.iAuthor);
lua_setfield(L, -2, "author");
push_string(L, prop.iSubject);
lua_setfield(L, -2, "subject");
push_string(L, prop.iKeywords);
lua_setfield(L, -2, "keywords");
push_string(L, prop.iPreamble);
lua_setfield(L, -2, "preamble");
push_string(L, prop.iCreated);
lua_setfield(L, -2, "created");
push_string(L, prop.iModified);
lua_setfield(L, -2, "modified");
push_string(L, prop.iCreator);
lua_setfield(L, -2, "creator");
lua_pushboolean(L, prop.iFullScreen);
lua_setfield(L, -2, "fullscreen");
lua_pushboolean(L, prop.iNumberPages);
lua_setfield(L, -2, "numberpages");
lua_pushstring(L, tex_engine_names[int(prop.iTexEngine)]);
lua_setfield(L, -2, "tex");
return 1;
}
static void propFlag(lua_State *L, const char *name, bool &flag)
{
lua_getfield(L, 2, name);
if (!lua_isnil(L, -1))
flag = lua_toboolean(L, -1);
lua_pop(L, 1);
}
static void propString(lua_State *L, const char *name, String &str)
{
lua_getfield(L, 2, name);
if (lua_isstring(L, -1))
str = lua_tolstring(L, -1, nullptr);
lua_pop(L, 1);
}
static int document_setProperties(lua_State *L)
{
Document **d = check_document(L, 1);
luaL_checktype(L, 2, LUA_TTABLE);
Document::SProperties prop = (*d)->properties();
// take from table
propFlag(L, "numberpages", prop.iNumberPages);
propFlag(L, "fullscreen", prop.iFullScreen);
propString(L, "title", prop.iTitle);
propString(L, "author", prop.iAuthor);
propString(L, "subject", prop.iSubject);
propString(L, "keywords", prop.iKeywords);
propString(L, "preamble", prop.iPreamble);
propString(L, "created", prop.iCreated);
propString(L, "modified", prop.iModified);
propString(L, "creator", prop.iCreator);
String tex;
propString(L, "tex", tex);
for (int i = 0; i < 4; ++i) {
if (!strcmp(tex.z(), tex_engine_names[i]))
prop.iTexEngine = LatexType(i);
}
(*d)->setProperties(prop);
return 0;
}
// --------------------------------------------------------------------
static const struct luaL_Reg document_methods[] = {
{ "__gc", document_destruct },
{ "__tostring", document_tostring },
{ "__len", document_len },
{ "__index", document_index },
{ "pages", document_pages },
{ "save", document_save },
{ "exportPages", document_exportPages },
{ "exportView", document_exportView },
{ "set", document_set },
{ "insert", document_insert },
{ "append", document_append },
{ "remove", document_remove },
{ "countTotalViews", document_countTotalViews },
{ "sheets", document_sheets },
{ "replaceSheets", document_replaceSheets },
{ "runLatex", document_runLatex },
{ "checkStyle", document_checkStyle },
{ "properties", document_properties },
{ "setProperties", document_setProperties },
{ nullptr, nullptr },
};
// --------------------------------------------------------------------
static int file_format(lua_State *L)
{
String fname = check_filename(L, 1);
FILE *fd = Platform::fopen(fname.z(), "rb");
if (!fd)
luaL_error(L, "fopen error: %s", strerror(errno));
FileSource source(fd);
FileFormat format = Document::fileFormat(source);
fclose(fd);
lua_pushstring(L, format_name[int(format)]);
return 1;
}
static int ipe_normalizeangle(lua_State *L)
{
Angle alpha(luaL_checknumber(L, 1));
double low = luaL_checknumber(L, 2);
lua_pushnumber(L, double(alpha.normalize(low)));
return 1;
}
static int ipe_splinetobeziers(lua_State *L)
{
luaL_argcheck(L, lua_istable(L, 1), 1, "argument is not a table");
std::vector v;
int no = lua_rawlen(L, 1);
for (int i = 1; i <= no; ++i) {
lua_rawgeti(L, 1, i);
luaL_argcheck(L, is_type(L, -1, "Ipe.vector"), 1,
"element is not a vector");
Vector *u = check_vector(L, -1);
v.push_back(*u);
lua_pop(L, 1);
}
bool closed = lua_toboolean(L, 2);
bool oldStyle = lua_toboolean(L, 3);
std::vector result;
if (closed)
Bezier::closedSpline(v.size(), &v[0], result);
else if (oldStyle)
Bezier::oldSpline(v.size(), &v[0], result);
else
Bezier::spline(v.size(), &v[0], result);
lua_createtable(L, result.size(), 0);
for (int i = 0; i < size(result); ++i) {
lua_createtable(L, 4, 1);
lua_pushliteral(L, "spline");
lua_setfield(L, -2, "type");
for (int k = 0; k < 4; ++k) {
if (k == 0 && i > 0)
push_vector(L, result[i-1].iV[3]);
else
push_vector(L, result[i].iV[k]);
lua_rawseti(L, -2, k+1);
}
lua_rawseti(L, -2, i+1);
}
return 1;
}
static int ipe_fileExists(lua_State *L)
{
String s = check_filename(L, 1);
lua_pushboolean(L, Platform::fileExists(s));
return 1;
}
static int ipe_realpath(lua_State *L)
{
String s = check_filename(L, 1);
push_string(L, Platform::realPath(s));
return 1;
}
static int ipe_directory(lua_State *L)
{
const char *path = luaL_checklstring(L, 1, nullptr);
std::vector files;
if (!Platform::listDirectory(path, files))
luaL_error(L, "cannot list directory '%s'", path);
lua_createtable(L, 0, files.size());
for (int i = 0; i < size(files); ++i) {
push_string(L, files[i]);
lua_rawseti(L, -2, i+1);
}
return 1;
}
#define l_checkmode(mode) \
(*mode != '\0' && strchr("rwa", *(mode++)) != nullptr && \
(*mode != '+' || ++mode) && /* skip if char is '+' */ \
(*mode != 'b' || ++mode) && /* skip if char is 'b' */ \
(*mode == '\0'))
static int ipe_fclose (lua_State *L) {
luaL_Stream *p = (luaL_Stream *) luaL_checkudata(L, 1, LUA_FILEHANDLE);
int res = fclose(p->f);
return luaL_fileresult(L, (res == 0), nullptr);
}
// open a file for reading or writing from Lua,
// correctly handling UTF-8 filenames on Windows
static int ipe_openFile(lua_State *L)
{
const char *filename = luaL_checklstring(L, 1, nullptr);
const char *mode = luaL_optlstring(L, 2, "r", nullptr);
luaL_Stream *p = (luaL_Stream *)lua_newuserdata(L, sizeof(luaL_Stream));
p->closef = nullptr; /* mark file handle as 'closed' */
luaL_setmetatable(L, LUA_FILEHANDLE);
p->f = nullptr;
p->closef = &ipe_fclose;
const char *md = mode; /* to traverse/check mode */
luaL_argcheck(L, l_checkmode(md), 2, "invalid mode");
p->f = Platform::fopen(filename, mode);
return (p->f == nullptr) ? luaL_fileresult(L, 0, filename) : 1;
}
static const char * const image_format_names[] = { "png", "jpeg" };
static int ipe_readImage(lua_State *L)
{
String s = check_filename(L, 1);
int fmt = luaL_checkoption(L, 2, nullptr, image_format_names);
Vector dotsPerInch;
const char *errmsg = nullptr;
Bitmap bmp = fmt ?
Bitmap::readJpeg(s.z(), dotsPerInch, errmsg) :
Bitmap::readPNG(s.z(), dotsPerInch, errmsg);
if (bmp.isNull()) {
lua_pushnil(L);
lua_pushstring(L, errmsg);
return 2;
}
Rect r(Vector::ZERO, Vector(bmp.width(), bmp.height()));
Image *img = new Image(r, bmp);
push_object(L, img);
push_vector(L, dotsPerInch);
return 2;
}
static int image_constructor(lua_State *L)
{
Rect *r = check_rect(L, 1);
Object *s = check_object(L, 2)->obj;
luaL_argcheck(L, s->type() == Object::EImage, 2, "not an image object");
Bitmap bm = s->asImage()->bitmap();
Image *img = new Image(*r, bm);
push_object(L, img);
return 1;
}
// --------------------------------------------------------------------
static const struct luaL_Reg ipelib_functions[] = {
{ "Document", document_constructor },
{ "Page", page_constructor },
{ "Vector", vector_constructor },
{ "Direction", direction_constructor },
{ "Matrix", matrix_constructor },
{ "Translation", translation_constructor },
{ "Rotation", rotation_constructor },
{ "Rect", rect_constructor },
{ "Line", line_constructor },
{ "LineThrough", line_through },
{ "Bisector", line_bisector },
{ "Segment", segment_constructor },
{ "Bezier", bezier_constructor },
{ "Quad", quad_constructor },
{ "Arc", arc_constructor },
{ "Reference", reference_constructor },
{ "Text", text_constructor },
{ "Path", path_constructor },
{ "Group", group_constructor },
{ "Object", xml_constructor },
{ "Sheet", sheet_constructor },
{ "Sheets", cascade_constructor },
{ "fileFormat", file_format },
{ "Ipelet", ipelet_constructor },
{ "normalizeAngle", ipe_normalizeangle },
{ "splineToBeziers", ipe_splinetobeziers },
{ "fileExists", ipe_fileExists },
{ "realPath", ipe_realpath },
{ "directory", ipe_directory },
{ "openFile", ipe_openFile },
{ "readImage", ipe_readImage },
{ "Image", image_constructor },
{ nullptr, nullptr }
};
extern "C" int luaopen_ipe(lua_State *L)
{
Platform::initLib(IPELIB_VERSION);
open_ipegeo(L);
open_ipeobj(L);
open_ipestyle(L);
open_ipepage(L);
open_ipelets(L);
luaL_newmetatable(L, "Ipe.document");
luaL_setfuncs(L, document_methods, 0);
lua_pop(L, 1);
luaL_newlib(L, ipelib_functions);
lua_setglobal(L, "ipe");
return 1;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelua/ipeluastyle.cpp 0000644 0001750 0001750 00000034432 13561570220 017354 0 ustar otfried otfried // --------------------------------------------------------------------
// ipeluastyle.cpp
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipelua.h"
#include "ipestyle.h"
#include "ipeiml.h"
#include
#include
using namespace ipe;
using namespace ipelua;
// --------------------------------------------------------------------
static const char * const set_names[] =
{ "preamble", "linecap", "linejoin", "fillrule", "symbol", "layout",
"gradient", nullptr };
int ipelua::test_option(lua_State *L, int i, const char * const *names)
{
const char *s = lua_tolstring(L, i, nullptr);
const char * const *p = names;
int what = 0;
while (*p) {
if (!strcmp(s, *p))
return what;
++what;
++p;
}
return -1;
}
// --------------------------------------------------------------------
void ipelua::push_sheet(lua_State *L, StyleSheet *s0, bool owned)
{
SSheet *s = (SSheet *) lua_newuserdata(L, sizeof(SSheet));
s->owned = owned;
s->sheet = s0;
luaL_getmetatable(L, "Ipe.sheet");
lua_setmetatable(L, -2);
}
int ipelua::sheet_constructor(lua_State *L)
{
if (lua_type(L, 1) == LUA_TSTRING) {
String fname = check_filename(L, 1);
FILE *fd = Platform::fopen(fname.z(), "rb");
if (!fd) {
lua_pushnil(L);
lua_pushfstring(L, "fopen error: %s", strerror(errno));
return 2;
}
FileSource source(fd);
ImlParser parser(source);
StyleSheet *sheet = parser.parseStyleSheet();
fclose(fd);
if (!sheet) {
lua_pushnil(L);
lua_pushfstring(L, "Parsing error at %d", parser.parsePosition());
return 2;
}
push_sheet(L, sheet);
} else if (lua_type(L, 2)== LUA_TSTRING) {
size_t len;;
const char *s = lua_tolstring(L, 2, &len);
Buffer buf(s, len);
BufferSource source(buf);
ImlParser parser(source);
StyleSheet *sheet = parser.parseStyleSheet();
if (!sheet) {
lua_pushnil(L);
lua_pushfstring(L, "Parsing error at %d", parser.parsePosition());
return 2;
}
push_sheet(L, sheet);
} else
push_sheet(L, new StyleSheet());
return 1;
}
static int sheet_clone(lua_State *L)
{
SSheet *p = check_sheet(L, 1);
push_sheet(L, new StyleSheet(*p->sheet));
return 1;
}
static int sheet_destructor(lua_State *L)
{
SSheet *s = check_sheet(L, 1);
if (s->owned)
delete s->sheet;
s->sheet = nullptr;
return 0;
}
static int sheet_tostring(lua_State *L)
{
check_sheet(L, 1);
lua_pushfstring(L, "Sheet@%p", lua_topointer(L, 1));
return 1;
}
// --------------------------------------------------------------------
// i must be positive
static Attribute check_absolute_attribute(Kind kind, lua_State *L, int i)
{
switch (kind) {
case EPen:
case ESymbolSize:
case EArrowSize:
case ETextSize:
case ETextStretch:
case EOpacity:
case EGridSize:
case EAngleSize: {
double v = luaL_checknumber(L, i);
return Attribute(Fixed::fromInternal(int(v * 1000 + 0.5))); }
case EColor: {
Color color = check_color(L, i);
return Attribute(color); }
case EDashStyle: {
const char *s = luaL_checklstring(L, i, nullptr);
Attribute ds = Attribute::makeDashStyle(s);
luaL_argcheck(L, !ds.isSymbolic(), i, "dashstyle is not absolute");
return ds; }
case ETextStyle:
case ELabelStyle:
case EEffect:
case ETiling:
case EGradient:
case ESymbol:
luaL_argerror(L, i, "cannot set absolute value of this kind");
break;
}
return Attribute::NORMAL(); // placate compiler
}
static int sheet_add(lua_State *L)
{
StyleSheet *s = check_sheet(L, 1)->sheet;
const char *what = luaL_checklstring(L, 2, nullptr);
if (!strcmp(what, "symbol")) {
const char *name = luaL_checklstring(L, 3, nullptr);
SObject *obj = check_object(L, 4);
Symbol symbol;
symbol.iObject = obj->obj->clone();
symbol.iTransformations = ETransformationsAffine;
s->addSymbol(Attribute(true, name), symbol);
} else {
Kind kind = Kind(luaL_checkoption(L, 2, nullptr, kind_names));
const char *name = luaL_checklstring(L, 3, nullptr);
Attribute sym(true, String(name));
Attribute value = check_absolute_attribute(kind, L, 4);
s->add(kind, sym, value);
}
return 0;
}
static int sheet_addfrom(lua_State *L)
{
StyleSheet *s = check_sheet(L, 1)->sheet;
StyleSheet *t = check_sheet(L, 2)->sheet;
Kind kind = Kind(luaL_checkoption(L, 3, nullptr, kind_names));
const char *name = luaL_checklstring(L, 4, nullptr);
Attribute sym(true, name);
switch (kind) {
case EGradient: {
const Gradient *g = t->findGradient(sym);
if (!g)
luaL_argerror(L, 4, "no such gradient");
s->addGradient(sym, *g);
break; }
case EEffect: {
const Effect *e = t->findEffect(sym);
if (!e)
luaL_argerror(L, 4, "no such effect");
s->addEffect(sym, *e);
break; }
case ETiling: {
const Tiling *g = t->findTiling(sym);
if (!g)
luaL_argerror(L, 4, "no such tiling");
s->addTiling(sym, *g);
break; }
default:
luaL_argerror(L, 3, "cannot handle this kind");
break;
}
return 0;
}
static int sheet_remove(lua_State *L)
{
StyleSheet *s = check_sheet(L, 1)->sheet;
Kind kind = Kind(luaL_checkoption(L, 2, nullptr, kind_names));
const char *name = luaL_checklstring(L, 3, nullptr);
Attribute sym(true, name);
s->remove(kind, sym);
return 0;
}
static int sheet_isStandard(lua_State *L)
{
SSheet *p = check_sheet(L, 1);
lua_pushboolean(L, p->sheet->isStandard());
return 1;
}
static int sheet_name(lua_State *L)
{
SSheet *p = check_sheet(L, 1);
String n = p->sheet->name();
if (n.empty())
lua_pushnil(L);
else
push_string(L, n);
return 1;
}
static int sheet_xml(lua_State *L)
{
SSheet *p = check_sheet(L, 1);
bool with_bitmaps = lua_toboolean(L, 2);
String data;
StringStream stream(data);
p->sheet->saveAsXml(stream, with_bitmaps);
push_string(L, data);
return 1;
}
static int sheet_setName(lua_State *L)
{
SSheet *p = check_sheet(L, 1);
const char *name = luaL_checklstring(L, 2, nullptr);
p->sheet->setName(name);
return 0;
}
static int sheet_set(lua_State *L)
{
StyleSheet *s = check_sheet(L, 1)->sheet;
int what = luaL_checkoption(L, 2, nullptr, set_names);
switch (what) {
case 0: // preamble
s->setPreamble(luaL_checklstring(L, 3, nullptr));
break;
case 1: // linecap
s->setLineCap(check_property(EPropLineCap, L, 3).lineCap());
break;
case 2: // linejoin
s->setLineJoin(check_property(EPropLineJoin, L, 3).lineJoin());
break;
case 3: // fillrule
s->setFillRule(check_property(EPropFillRule, L, 3).fillRule());
break;
default:
luaL_argerror(L, 2, "invalid kind for 'set'");
break;
}
return 0;
}
// --------------------------------------------------------------------
static const struct luaL_Reg sheet_methods[] = {
{ "__gc", sheet_destructor },
{ "__tostring", sheet_tostring },
{ "clone", sheet_clone },
{ "xml", sheet_xml },
{ "add", sheet_add },
{ "addFrom", sheet_addfrom },
{ "remove", sheet_remove },
{ "set", sheet_set },
{ "isStandard", sheet_isStandard },
{ "name", sheet_name },
{ "setName", sheet_setName },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
void ipelua::push_cascade(lua_State *L, Cascade *s0, bool owned)
{
SCascade *s = (SCascade *) lua_newuserdata(L, sizeof(SCascade));
s->owned = owned;
s->cascade = s0;
luaL_getmetatable(L, "Ipe.cascade");
lua_setmetatable(L, -2);
}
int ipelua::cascade_constructor(lua_State *L)
{
push_cascade(L, new Cascade());
return 1;
}
static int cascade_clone(lua_State *L)
{
SCascade *s = check_cascade(L, 1);
push_cascade(L, new Cascade(*s->cascade));
return 1;
}
static int cascade_destructor(lua_State *L)
{
SCascade *s = check_cascade(L, 1);
if (s->owned)
delete s->cascade;
s->cascade = nullptr;
return 0;
}
static int cascade_tostring(lua_State *L)
{
check_cascade(L, 1);
lua_pushfstring(L, "Cascade@%p", lua_topointer(L, 1));
return 1;
}
// --------------------------------------------------------------------
// also works for symbol, gradient, tiling
static int cascade_allNames(lua_State *L)
{
SCascade *p = check_cascade(L, 1);
Kind kind = Kind(luaL_checkoption (L, 2, nullptr, kind_names));
AttributeSeq seq;
p->cascade->allNames(kind, seq);
lua_createtable(L, seq.size(), 0);
for (int i = 0; i < size(seq); ++i) {
push_string(L, seq[i].string());
lua_rawseti(L, -2, i + 1);
}
return 1;
}
static int push_layout(lua_State *L, const Layout &l)
{
lua_createtable(L, 0, 7);
push_vector(L, l.iPaperSize);
lua_setfield(L, -2, "papersize");
push_vector(L, l.iOrigin);
lua_setfield(L, -2, "origin");
push_vector(L, l.iFrameSize);
lua_setfield(L, -2, "framesize");
lua_pushnumber(L, l.iParagraphSkip);
lua_setfield(L, -2, "paragraph_skip");
lua_pushboolean(L, l.iCrop);
lua_setfield(L, -2, "crop");
return 1;
}
static int push_gradient(lua_State *L, const Gradient &g)
{
lua_createtable(L, 0, 6);
lua_pushstring(L, (g.iType == Gradient::EAxial) ? "axial" : "radial");
lua_setfield(L, -2, "type");
lua_createtable(L, 2, 0);
push_vector(L, g.iV[0]);
lua_rawseti(L, -2, 1);
push_vector(L, g.iV[1]);
lua_rawseti(L, -2, 2);
lua_setfield(L, -2, "v");
lua_pushboolean(L, g.iExtend);
lua_setfield(L, -2, "extend");
push_matrix(L, g.iMatrix);
lua_setfield(L, -2, "matrix");
if (g.iType == Gradient::ERadial) {
lua_createtable(L, 2, 0);
lua_pushnumber(L, g.iRadius[0]);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, g.iRadius[1]);
lua_rawseti(L, -2, 2);
lua_setfield(L, -2, "radius");
}
lua_createtable(L, g.iStops.size(), 0);
for (int i = 0; i < int(g.iStops.size()); ++i) {
lua_createtable(L, 0, 2);
lua_pushnumber(L, g.iStops[i].offset);
lua_setfield(L, -2, "offset");
push_color(L, g.iStops[i].color);
lua_setfield(L, -2, "color");
lua_rawseti(L, -2, i+1);
}
lua_setfield(L, -2, "stops");
return 1;
}
// find will also work for the values that are "set"
static int cascade_find(lua_State *L)
{
Cascade *s = check_cascade(L, 1)->cascade;
luaL_checktype(L, 2, LUA_TSTRING);
int what = test_option(L, 2, set_names);
switch (what) {
case 0: // preamble
push_string(L, s->findPreamble());
break;
case 1: // linecap
push_attribute(L, Attribute(s->lineCap()));
break;
case 2: // linejoin
push_attribute(L, Attribute(s->lineJoin()));
break;
case 3: // fillrule
push_attribute(L, Attribute(s->fillRule()));
break;
case 4: { // symbol
const char *name = luaL_checklstring(L, 3, nullptr);
const Symbol *symbol = s->findSymbol(Attribute(true, name));
if (symbol)
push_object(L, symbol->iObject->clone());
else
lua_pushnil(L);
break; }
case 5: // layout
push_layout(L, *s->findLayout());
break;
case 6: { // gradient
const char *name = luaL_checklstring(L, 3, nullptr);
push_gradient(L, *s->findGradient(Attribute(true, name)));
break; }
default: {
Kind kind = Kind(luaL_checkoption(L, 2, nullptr, kind_names));
const char *name = luaL_checklstring(L, 3, nullptr);
push_attribute(L, s->find(kind, Attribute(true, String(name))));
break; }
}
return 1;
}
static int cascade_has(lua_State *L)
{
Cascade *p = check_cascade(L, 1)->cascade;
Kind kind = Kind(luaL_checkoption(L, 2, nullptr, kind_names));
const char *name = luaL_checklstring(L, 3, nullptr);
Attribute sym(true, String(name));
lua_pushboolean(L, p->has(kind, sym));
return 1;
}
static int cascade_count(lua_State *L)
{
Cascade *p = check_cascade(L, 1)->cascade;
lua_pushnumber(L, p->count());
return 1;
}
static int cascade_sheet(lua_State *L)
{
Cascade *p = check_cascade(L, 1)->cascade;
int index = (int)luaL_checkinteger(L, 2);
luaL_argcheck(L, 1 <= index && index <= p->count(), 2, "index out of bounds");
push_sheet(L, p->sheet(index - 1), false);
return 1;
}
static int cascade_insert(lua_State *L)
{
Cascade *p = check_cascade(L, 1)->cascade;
int index = (int)luaL_checkinteger(L, 2);
luaL_argcheck(L, 1 <= index && index <= p->count() + 1,
2, "index out of bounds");
SSheet *s = check_sheet(L, 3);
StyleSheet *sheet = s->sheet;
if (!s->owned)
sheet = new StyleSheet(*s->sheet);
p->insert(index - 1, sheet);
s->owned = false; // now owned by cascade
return 0;
}
static int cascade_remove(lua_State *L)
{
Cascade *p = check_cascade(L, 1)->cascade;
int index = (int)luaL_checkinteger(L, 2);
luaL_argcheck(L, 1 <= index && index <= p->count(),
2, "index out of bounds");
p->remove(index - 1);
return 0;
}
// --------------------------------------------------------------------
static const struct luaL_Reg cascade_methods[] = {
{ "__gc", cascade_destructor },
{ "__tostring", cascade_tostring },
{ "clone", cascade_clone },
{ "allNames", cascade_allNames },
{ "find", cascade_find },
{ "has", cascade_has },
{ "count", cascade_count },
{ "sheet", cascade_sheet },
{ "insert", cascade_insert },
{ "remove", cascade_remove },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
int ipelua::open_ipestyle(lua_State *L)
{
make_metatable(L, "Ipe.sheet", sheet_methods);
make_metatable(L, "Ipe.cascade", cascade_methods);
return 0;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelua/ipeluaipelet.cpp 0000644 0001750 0001750 00000021062 13561570220 017471 0 ustar otfried otfried // --------------------------------------------------------------------
// ipeluaipelets.cpp
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipelua.h"
#ifdef WIN32
#include
#else
#include
#include
#endif
using namespace ipe;
using namespace ipelua;
typedef Ipelet *(*PNewIpeletFn)();
// --------------------------------------------------------------------
static void snapFlag(lua_State *L, int &flags, const char *mode, uint32_t bits)
{
lua_getfield(L, -1, mode);
if (!lua_isnil(L, -1))
flags = lua_toboolean(L, -1) ? (flags | bits) : (flags & ~bits);
lua_pop(L, 1);
}
void ipelua::get_snap(lua_State *L, int i, ipe::Snap &snap)
{
luaL_checktype(L, i, LUA_TTABLE);
snapFlag(L, snap.iSnap, "snapvtx", Snap::ESnapVtx);
snapFlag(L, snap.iSnap, "snapctl", Snap::ESnapCtl);
snapFlag(L, snap.iSnap, "snapbd", Snap::ESnapBd);
snapFlag(L, snap.iSnap, "snapint", Snap::ESnapInt);
snapFlag(L, snap.iSnap, "snapgrid", Snap::ESnapGrid);
snapFlag(L, snap.iSnap, "snapangle", Snap::ESnapAngle);
snapFlag(L, snap.iSnap, "snapauto", Snap::ESnapAuto);
lua_getfield(L, i, "grid_visible");
if (!lua_isnil(L, -1))
snap.iGridVisible = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, i, "gridsize");
if (!lua_isnil(L, -1))
snap.iGridSize = (int) luaL_checkinteger(L, -1);
lua_pop(L, 1);
lua_getfield(L, i, "anglesize");
if (!lua_isnil(L, -1))
snap.iAngleSize = IpePi * luaL_checknumber(L, -1) / 180.0;
lua_pop(L, 1);
lua_getfield(L, i, "snap_distance");
if (!lua_isnil(L, -1))
snap.iSnapDistance = (int) luaL_checkinteger(L, -1);
lua_pop(L, 1);
lua_getfield(L, i, "with_axes");
if (!lua_isnil(L, -1))
snap.iWithAxes = lua_toboolean(L, -1);
lua_pop(L, 1);
lua_getfield(L, i, "origin");
if (is_type(L, -1, "Ipe.vector"))
snap.iOrigin = *check_vector(L, -1);
lua_pop(L, 1);
lua_getfield(L, i, "orientation");
if (!lua_isnil(L, -1))
snap.iDir = luaL_checknumber(L, -1);
lua_pop(L, 1);
}
// --------------------------------------------------------------------
static int ipelet_destructor(lua_State *L)
{
ipeDebug("Ipelet destructor");
Ipelet **p = check_ipelet(L, 1);
delete *p;
*p = nullptr;
return 0;
}
static int ipelet_tostring(lua_State *L)
{
check_ipelet(L, 1);
lua_pushfstring(L, "Ipelet@%p", lua_topointer(L, 1));
return 1;
}
// --------------------------------------------------------------------
int ipelua::ipelet_constructor(lua_State *L)
{
String fname(luaL_checklstring(L, 1, nullptr));
#if defined(WIN32)
String dllname = fname + ".dll";
#else
String dllname = fname + ".so";
#endif
ipeDebug("Loading dll '%s'", dllname.z());
PNewIpeletFn pIpelet = nullptr;
#ifdef WIN32
HMODULE hMod = LoadLibraryW(dllname.w().data());
if (hMod) {
pIpelet = (PNewIpeletFn) GetProcAddress(hMod, "newIpelet");
if (!pIpelet)
pIpelet = (PNewIpeletFn) GetProcAddress(hMod, "_newIpelet");
}
#else
void *handle = dlopen(dllname.z(), RTLD_NOW);
if (handle) {
pIpelet = (PNewIpeletFn) dlsym(handle, "newIpelet");
if (!pIpelet)
pIpelet = (PNewIpeletFn) dlsym(handle, "_newIpelet");
}
#endif
if (pIpelet) {
Ipelet **p = (Ipelet **) lua_newuserdata(L, sizeof(Ipelet *));
*p = nullptr;
luaL_getmetatable(L, "Ipe.ipelet");
lua_setmetatable(L, -2);
Ipelet *ipelet = pIpelet();
if (ipelet == nullptr) {
lua_pushnil(L);
lua_pushstring(L, "ipelet returns no object");
return 2;
}
if (ipelet->ipelibVersion() != IPELIB_VERSION) {
delete ipelet;
lua_pushnil(L);
lua_pushstring(L, "ipelet linked against older version of Ipelib");
return 2;
}
*p = ipelet;
ipeDebug("Ipelet '%s' loaded", fname.z());
return 1;
} else {
lua_pushnil(L);
#ifdef WIN32
if (hMod)
lua_pushfstring(L, "Error %d finding DLL entry point", GetLastError());
else
lua_pushfstring(L, "Error %d loading Ipelet DLL '%s'", GetLastError(),
dllname.z());
#else
lua_pushfstring(L, "Error loading Ipelet '%s': %s", dllname.z(), dlerror());
#if defined(__APPLE__)
ipeDebug("DYLD_LIBRARY_PATH=%s", getenv("DYLD_LIBRARY_PATH"));
ipeDebug("DYLD_FALLBACK_LIBRARY_PATH=%s", getenv("DYLD_FALLBACK_LIBRARY_PATH"));
#endif
#endif
return 2;
}
}
// --------------------------------------------------------------------
class Helper : public IpeletHelper {
public:
Helper(lua_State *L0, int luahelper);
~Helper();
virtual void message(const char *msg);
virtual int messageBox(const char *text, const char *details,
int buttons);
virtual bool getString(const char *prompt, String &str);
virtual String getParameter(const char *key);
private:
lua_State *L;
int iHelper;
};
Helper::Helper(lua_State *L0, int luahelper)
{
L = L0;
iHelper = luahelper;
}
Helper::~Helper()
{
luaL_unref(L, LUA_REGISTRYINDEX, iHelper);
}
void Helper::message(const char *msg)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, iHelper);
lua_getfield(L, -1, "message");
lua_pushvalue(L, -2); // luahelper
lua_remove(L, -3);
lua_pushstring(L, msg);
luacall(L, 2, 0);
}
int Helper::messageBox(const char *text, const char *details, int buttons)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, iHelper);
lua_getfield(L, -1, "messageBox");
lua_pushvalue(L, -2); // luahelper
lua_remove(L, -3);
lua_pushstring(L, text);
if (details)
lua_pushstring(L, details);
else
lua_pushnil(L);
lua_pushnumber(L, buttons);
luacall(L, 4, 1);
if (lua_isnumber(L, -1))
return int(lua_tonumberx(L, -1, nullptr));
else
return 0;
}
bool Helper::getString(const char *prompt, String &str)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, iHelper);
lua_getfield(L, -1, "getString");
lua_pushvalue(L, -2); // luahelper
lua_remove(L, -3);
lua_pushstring(L, prompt);
push_string(L, str);
luacall(L, 3, 1);
if (lua_isstring(L, -1)) {
str = lua_tolstring(L, -1, nullptr);
return true;
} else
return false;
}
String Helper::getParameter(const char *key)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, iHelper);
lua_getfield(L, -1, "parameters");
String value;
if (lua_istable(L, -1)) {
lua_getfield(L, -1, key);
const char *t = lua_tolstring(L, -1, nullptr);
if (t)
value = t;
lua_pop(L, 1); // parameters[key]
}
lua_pop(L, 2); // helper, parameters
return value;
}
// --------------------------------------------------------------------
static int ipelet_run(lua_State *L)
{
Ipelet **p = check_ipelet(L, 1);
int num = (int)luaL_checkinteger(L, 2) - 1; // Lua counts starting from one
IpeletData data;
data.iPage = check_page(L, 3)->page;
data.iDoc = *check_document(L, 4);
data.iPageNo = (int)luaL_checkinteger(L, 5);
data.iView = (int)luaL_checkinteger(L, 6);
data.iLayer = check_layer(L, 7, data.iPage);
check_allattributes(L, 8, data.iAttributes);
get_snap(L, 9, data.iSnap);
lua_pushvalue(L, 10);
int luahelper = luaL_ref(L, LUA_REGISTRYINDEX);
Helper helper(L, luahelper);
bool result = (*p)->run(num, &data, &helper);
lua_pushboolean(L, result);
return 1;
}
// --------------------------------------------------------------------
static const struct luaL_Reg ipelet_methods[] = {
{ "__tostring", ipelet_tostring },
{ "__gc", ipelet_destructor },
{ "run", ipelet_run },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
int ipelua::open_ipelets(lua_State *L)
{
make_metatable(L, "Ipe.ipelet", ipelet_methods);
return 0;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelua/ipeluapage.cpp 0000644 0001750 0001750 00000042263 13561570220 017131 0 ustar otfried otfried // --------------------------------------------------------------------
// ipeluapage.cpp
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipelua.h"
#include "ipepage.h"
#include "ipeiml.h"
using namespace ipe;
using namespace ipelua;
// --------------------------------------------------------------------
void ipelua::push_page(lua_State *L, Page *page, bool owned)
{
SPage *p = (SPage *) lua_newuserdata(L, sizeof(SPage));
p->owned = owned;
luaL_getmetatable(L, "Ipe.page");
lua_setmetatable(L, -2);
p->page = page;
}
static int check_objno(lua_State *L, int i, Page *p, int extra = 0)
{
int n = (int)luaL_checkinteger(L, i);
luaL_argcheck(L, 1 <= n && n <= p->count() + extra,
i, "invalid object index");
return n - 1;
}
int ipelua::check_layer(lua_State *L, int i, Page *p)
{
const char *name = luaL_checklstring(L, i, nullptr);
int l = p->findLayer(name);
luaL_argcheck(L, l >= 0, i, "layer does not exist");
return l;
}
int ipelua::check_viewno(lua_State *L, int i, Page *p, int extra)
{
int n = (int)luaL_checkinteger(L, i);
luaL_argcheck(L, 1 <= n && n <= p->countViews() + extra,
i, "invalid view index");
return n - 1;
}
int ipelua::page_constructor(lua_State *L)
{
if (lua_isnoneornil(L, 1)) {
push_page(L, Page::basic());
return 1;
} else {
size_t len;
const char *p = luaL_checklstring(L, 1, &len);
Buffer data(p, len);
BufferSource source(data);
ImlParser parser(source);
Page *page = parser.parsePageSelection();
if (page) {
push_page(L, page);
return 1;
} else
return 0;
}
}
static int page_destructor(lua_State *L)
{
SPage *p = check_page(L, 1);
if (p->owned)
delete p->page;
p->page = nullptr;
return 0;
}
static int page_index(lua_State *L)
{
Page *p = check_page(L, 1)->page;
if (lua_type(L, 2) == LUA_TNUMBER) {
int n = check_objno(L, 2, p);
push_object(L, p->object(n), false);
} else {
const char *key = luaL_checklstring(L, 2, nullptr);
if (!luaL_getmetafield(L, 1, key))
lua_pushnil(L);
}
return 1;
}
static int page_tostring(lua_State *L)
{
check_page(L, 1);
lua_pushfstring(L, "Page@%p", lua_topointer(L, 1));
return 1;
}
static int page_len(lua_State *L)
{
Page *p = check_page(L, 1)->page;
lua_pushinteger(L, p->count());
return 1;
}
static int page_clone(lua_State *L)
{
Page *p = check_page(L, 1)->page;
push_page(L, new Page(*p));
return 1;
}
// --------------------------------------------------------------------
static void push_select(lua_State *L, TSelect sel)
{
if (sel == ENotSelected)
lua_pushnil(L);
else if (sel == EPrimarySelected)
lua_pushnumber(L, 1);
else
lua_pushnumber(L, 2);
}
static TSelect check_select(lua_State *L, int index)
{
TSelect w = ENotSelected;
if (!lua_isnoneornil(L, index)) {
if ((int)luaL_checkinteger(L, index) == 1)
w = EPrimarySelected;
else
w = ESecondarySelected;
}
return w;
}
// arguments: page, counter
static int page_object_iterator(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int i = (int)luaL_checkinteger(L, 2);
i = i + 1;
if (i <= p->count()) {
lua_pushinteger(L, i); // new counter
push_object(L, p->object(i-1), false); // object
push_select(L, p->select(i-1));
push_string(L, p->layer(p->layerOf(i-1))); // layer
return 4;
} else
return 0;
}
// returns object iterator for use in for loop
// returns iterator function, invariant state, control variable
static int page_objects(lua_State *L)
{
(void) check_page(L, 1);
lua_pushcfunction(L, page_object_iterator); // iterator function
lua_pushvalue(L, 1); // page
lua_pushinteger(L, 0); // counter
return 3;
}
// --------------------------------------------------------------------
static int page_xml(lua_State *L)
{
static const char * const option_names[] =
{ "ipepage", "ipeselection", nullptr };
Page *p = check_page(L, 1)->page;
int t = luaL_checkoption(L, 2, nullptr, option_names);
String data;
StringStream stream(data);
if (t == 0)
p->saveAsIpePage(stream);
else if (t == 1)
p->saveSelection(stream);
push_string(L, data);
return 1;
}
static int page_layers(lua_State *L)
{
Page *p = check_page(L, 1)->page;
lua_createtable(L, 0, p->countLayers());
for (int i = 0; i < p->countLayers(); ++i) {
push_string(L, p->layer(i));
lua_rawseti(L, -2, i + 1);
}
return 1;
}
static int page_countLayers(lua_State *L)
{
Page *p = check_page(L, 1)->page;
lua_pushinteger(L, p->countLayers());
return 1;
}
static int page_isLocked(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_layer(L, 2, p);
lua_pushboolean(L, p->isLocked(n));
return 1;
}
static int page_hasSnapping(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_layer(L, 2, p);
lua_pushboolean(L, p->hasSnapping(n));
return 1;
}
static int page_setLocked(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_layer(L, 2, p);
p->setLocked(n, lua_toboolean(L, 3));
return 0;
}
static int page_setSnapping(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_layer(L, 2, p);
p->setSnapping(n, lua_toboolean(L, 3));
return 0;
}
static int page_renameLayer(lua_State *L)
{
Page *p = check_page(L, 1)->page;
const char *s1 = luaL_checklstring(L, 2, nullptr);
const char *s2 = luaL_checklstring(L, 3, nullptr);
p->renameLayer(s1, s2);
return 0;
}
static int page_addLayer(lua_State *L)
{
Page *p = check_page(L, 1)->page;
if (lua_isnoneornil(L, 2)) {
p->addLayer();
} else {
const char *s = luaL_checklstring(L, 2, nullptr);
p->addLayer(s);
}
push_string(L, p->layer(p->countLayers() - 1));
return 1;
}
static int page_removeLayer(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_layer(L, 2, p);
p->removeLayer(p->layer(n));
return 0;
}
static int page_moveLayer(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int index = check_layer(L, 2, p);
int newIndex = (int)luaL_checkinteger(L, 3) - 1;
luaL_argcheck(L, 0 <= newIndex && newIndex < p->countLayers(),
3, "invalid target index");
p->moveLayer(index, newIndex);
return 0;
}
// --------------------------------------------------------------------
static int page_select(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_objno(L, 2, p);
push_select(L, p->select(n));
return 1;
}
static int page_setSelect(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_objno(L, 2, p);
TSelect w = check_select(L, 3);
p->setSelect(n, w);
return 0;
}
static int page_layerOf(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_objno(L, 2, p);
push_string(L, p->layer(p->layerOf(n)));
return 1;
}
static int page_setLayerOf(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_objno(L, 2, p);
const char *s = luaL_checklstring(L, 3, nullptr);
int l = p->findLayer(s);
luaL_argcheck(L, l >= 0, 3, "layer does not exist");
p->setLayerOf(n, l);
return 0;
}
static int page_bbox(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_objno(L, 2, p);
push_rect(L, p->bbox(n));
return 1;
}
// use index nil to append
static int page_insert(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n;
if (lua_isnil(L, 2))
n = p->count();
else
n = check_objno(L, 2, p, 1);
SObject *obj = check_object(L, 3);
TSelect select = check_select(L, 4);
int l = check_layer(L, 5, p);
p->insert(n, select, l, obj->obj->clone());
return 0;
}
static int page_remove(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_objno(L, 2, p);
p->remove(n);
return 0;
}
static int page_replace(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_objno(L, 2, p);
SObject *obj = check_object(L, 3);
p->replace(n, obj->obj->clone());
return 0;
}
static int page_invalidateBBox(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_objno(L, 2, p);
p->invalidateBBox(n);
return 0;
}
static int page_transform(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_objno(L, 2, p);
Matrix *m = check_matrix(L, 3);
p->transform(n, *m);
return 0;
}
static int page_distance(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_objno(L, 2, p);
Vector *v = check_vector(L, 3);
double bound = luaL_checknumber(L, 4);
lua_pushnumber(L, p->distance(n, *v, bound));
return 1;
}
static int page_setAttribute(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_objno(L, 2, p);
Property prop = Property(luaL_checkoption(L, 3, nullptr, property_names));
Attribute value = check_property(prop, L, 4);
lua_pushboolean(L, p->setAttribute(n, prop, value));
return 1;
}
static int page_primarySelection(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int prim = p->primarySelection();
if (prim >= 0) {
lua_pushnumber(L, prim + 1);
return 1;
} else
return 0;
}
static int page_hasSelection(lua_State *L)
{
Page *p = check_page(L, 1)->page;
lua_pushboolean(L, p->hasSelection());
return 1;
}
static int page_deselectAll(lua_State *L)
{
Page *p = check_page(L, 1)->page;
p->deselectAll();
return 0;
}
static int page_ensurePrimarySelection(lua_State *L)
{
Page *p = check_page(L, 1)->page;
p->ensurePrimarySelection();
return 0;
}
static int page_titles(lua_State *L)
{
Page *p = check_page(L, 1)->page;
lua_createtable(L, 3, 0);
push_string(L, p->title());
lua_setfield(L, -2, "title");
if (!p->sectionUsesTitle(0)) {
push_string(L, p->section(0));
lua_setfield(L, -2, "section");
}
if (!p->sectionUsesTitle(1)) {
push_string(L, p->section(1));
lua_setfield(L, -2, "subsection");
}
return 1;
}
static int page_setTitles(lua_State *L)
{
Page *p = check_page(L, 1)->page;
luaL_checktype(L, 2, LUA_TTABLE);
lua_getfield(L, 2, "title");
if (lua_isstring(L, -1))
p->setTitle(lua_tolstring(L, -1, nullptr));
lua_getfield(L, 2, "section");
if (lua_isstring(L, -1))
p->setSection(0, false, lua_tolstring(L, -1, nullptr));
else
p->setSection(0, true, "");
lua_getfield(L, 2, "subsection");
if (lua_isstring(L, -1))
p->setSection(1, false, lua_tolstring(L, -1, nullptr));
else
p->setSection(1, true, "");
lua_pop(L, 3); // title, section, subsection
return 0;
}
static int page_notes(lua_State *L)
{
Page *p = check_page(L, 1)->page;
push_string(L, p->notes());
return 1;
}
static int page_setNotes(lua_State *L)
{
Page *p = check_page(L, 1)->page;
String n = luaL_checklstring(L, 2, nullptr);
p->setNotes(n);
return 0;
}
static int page_marked(lua_State *L)
{
Page *p = check_page(L, 1)->page;
lua_pushboolean(L, p->marked());
return 1;
}
static int page_setMarked(lua_State *L)
{
Page *p = check_page(L, 1)->page;
p->setMarked(lua_toboolean(L, 2));
return 0;
}
// --------------------------------------------------------------------
static int page_countViews(lua_State *L)
{
Page *p = check_page(L, 1)->page;
lua_pushinteger(L, p->countViews());
return 1;
}
static int page_effect(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_viewno(L, 2, p);
push_string(L, p->effect(n).string());
return 1;
}
static int page_setEffect(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_viewno(L, 2, p);
const char *eff = luaL_checklstring(L, 3, nullptr);
p->setEffect(n, Attribute(true, eff));
return 0;
}
static int page_active(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_viewno(L, 2, p);
push_string(L, p->active(n));
return 1;
}
static int page_setActive(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_viewno(L, 2, p);
const char *name = luaL_checklstring(L, 3, nullptr);
p->setActive(n, name);
return 0;
}
static int page_insertView(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_viewno(L, 2, p, 1);
const char *name = luaL_checklstring(L, 3, nullptr);
p->insertView(n, name);
return 0;
}
static int page_removeView(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_viewno(L, 2, p);
p->removeView(n);
return 0;
}
static int page_clearViews(lua_State *L)
{
Page *p = check_page(L, 1)->page;
p->clearViews();
return 0;
}
static int page_markedView(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_viewno(L, 2, p);
lua_pushboolean(L, p->markedView(n));
return 1;
}
static int page_setMarkedView(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_viewno(L, 2, p);
p->setMarkedView(n, lua_toboolean(L, 3));
return 0;
}
static int page_viewName(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_viewno(L, 2, p);
push_string(L, p->viewName(n));
return 1;
}
static int page_setViewName(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int n = check_viewno(L, 2, p);
const char *s = luaL_checklstring(L, 3, nullptr);
p->setViewName(n, s);
return 0;
}
// Either: view & layername
// Or: view & object index
static int page_visible(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int vno = check_viewno(L, 2, p);
if (lua_type(L, 3) == LUA_TNUMBER) {
int objno = check_objno(L, 3, p);
lua_pushboolean(L, p->objectVisible(vno, objno));
} else {
int l = check_layer(L, 3, p);
lua_pushboolean(L, p->visible(vno, l));
}
return 1;
}
static int page_setVisible(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int vno = check_viewno(L, 2, p);
int l = check_layer(L, 3, p);
bool vis = lua_toboolean(L, 4);
p->setVisible(vno, p->layer(l), vis);
return 0;
}
// --------------------------------------------------------------------
static int page_findedge(lua_State *L)
{
Page *p = check_page(L, 1)->page;
int view = check_viewno(L, 2, p, 0);
Vector pos = *check_vector(L, 3);
Snap snap;
if (!snap.setEdge(pos, p, view))
return 0;
push_vector(L, snap.iOrigin);
lua_pushnumber(L, snap.iDir);
return 2;
}
// --------------------------------------------------------------------
static const struct luaL_Reg page_methods[] = {
{ "__index", page_index },
{ "__tostring", page_tostring },
{ "__gc", page_destructor },
{ "__len", page_len },
{ "clone", page_clone },
{ "objects", page_objects },
{ "countViews", page_countViews },
{ "countLayers", page_countLayers },
{ "xml", page_xml },
{ "layers", page_layers },
{ "isLocked", page_isLocked },
{ "hasSnapping", page_hasSnapping },
{ "setLocked", page_setLocked },
{ "setSnapping", page_setSnapping },
{ "renameLayer", page_renameLayer },
{ "addLayer", page_addLayer },
{ "removeLayer", page_removeLayer },
{ "moveLayer", page_moveLayer },
{ "select", page_select },
{ "setSelect", page_setSelect },
{ "layerOf", page_layerOf },
{ "setLayerOf", page_setLayerOf },
{ "effect", page_effect },
{ "setEffect", page_setEffect },
{ "active", page_active },
{ "setActive", page_setActive },
{ "insertView", page_insertView },
{ "removeView", page_removeView },
{ "clearViews", page_clearViews },
{ "markedView", page_markedView },
{ "setMarkedView", page_setMarkedView },
{ "viewName", page_viewName },
{ "setViewName", page_setViewName },
{ "visible", page_visible },
{ "setVisible", page_setVisible },
{ "bbox", page_bbox },
{ "insert", page_insert },
{ "remove", page_remove },
{ "replace", page_replace },
{ "invalidateBBox", page_invalidateBBox },
{ "transform", page_transform },
{ "distance", page_distance },
{ "setAttribute", page_setAttribute },
{ "primarySelection", page_primarySelection },
{ "hasSelection", page_hasSelection },
{ "deselectAll", page_deselectAll },
{ "ensurePrimarySelection", page_ensurePrimarySelection },
{ "findEdge", page_findedge },
{ "titles", page_titles },
{ "setTitles", page_setTitles },
{ "notes", page_notes },
{ "setNotes", page_setNotes },
{ "marked", page_marked },
{ "setMarked", page_setMarked },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
int ipelua::open_ipepage(lua_State *L)
{
luaL_newmetatable(L, "Ipe.page");
luaL_setfuncs(L, page_methods, 0);
lua_pop(L, 1);
return 0;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelua/Makefile 0000644 0001750 0001750 00000002073 13561570220 015744 0 ustar otfried otfried # --------------------------------------------------------------------
# Makefile for Lua bindings for ipe
# --------------------------------------------------------------------
OBJDIR = $(BUILDDIR)/obj/ipelua
include ../common.mak
TARGET = $(call dll_target,ipelua)
MAKE_SYMLINKS = $(call dll_symlinks,ipelua)
SONAME = $(call soname,ipelua)
INSTALL_SYMLINKS = $(call install_symlinks,ipelua)
CPPFLAGS += -I../include $(LUA_CFLAGS)
LIBS += -L$(buildlib) -lipe $(LUA_LIBS) $(DL_LIBS)
CXXFLAGS += $(DLL_CFLAGS)
all: $(TARGET)
sources = \
ipelib.cpp \
ipeluageo.cpp \
ipeluaobj.cpp \
ipeluastyle.cpp \
ipeluapage.cpp \
ipeluaipelet.cpp
$(TARGET): $(objects)
$(MAKE_LIBDIR)
$(CXX) $(LDFLAGS) $(DLL_LDFLAGS) $(SONAME) -o $@ $^ $(LIBS)
$(MAKE_SYMLINKS)
clean:
@-rm -f $(objects) $(TARGET) $(DEPEND)
$(DEPEND): Makefile
$(MAKE_DEPEND)
-include $(DEPEND)
install: $(TARGET)
$(INSTALL_DIR) $(INSTALL_ROOT)$(IPELIBDIR)
$(INSTALL_PROGRAMS) $(TARGET) $(INSTALL_ROOT)$(IPELIBDIR)
$(INSTALL_SYMLINKS)
# --------------------------------------------------------------------
ipe-7.2.13/src/ipelua/ipeluageo.cpp 0000644 0001750 0001750 00000053226 13561570220 016770 0 ustar otfried otfried // --------------------------------------------------------------------
// ipeluageo.cpp
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipelua.h"
#include
using namespace ipe;
using namespace ipelua;
// --------------------------------------------------------------------
void ipelua::push_vector(lua_State *L, const Vector &v0)
{
Vector *v = (Vector *) lua_newuserdata(L, sizeof(Vector));
luaL_getmetatable(L, "Ipe.vector");
lua_setmetatable(L, -2);
new (v) Vector(v0);
}
int ipelua::vector_constructor(lua_State *L)
{
if (lua_gettop(L) == 0)
push_vector(L, Vector::ZERO);
else
push_vector(L, Vector(luaL_checknumber(L, 1), luaL_checknumber(L, 2)));
return 1;
}
int ipelua::direction_constructor(lua_State *L)
{
Angle alpha(luaL_checknumber(L, 1));
push_vector(L, Vector(alpha));
return 1;
}
static int vector_get(lua_State *L)
{
Vector *v = check_vector(L, 1);
const char *key = lua_tolstring(L, 2, nullptr);
if (!strcmp(key, "x"))
lua_pushnumber(L, v->x);
else if (!strcmp(key, "y"))
lua_pushnumber(L, v->y);
else if (!luaL_getmetafield(L, 1, key))
lua_pushnil(L);
return 1;
}
static int vector_tostring(lua_State *L)
{
Vector *v = check_vector(L, 1);
lua_pushfstring(L, "(%f, %f)", v->x, v->y);
return 1;
}
static int vector_add(lua_State *L)
{
Vector *v1 = check_vector(L, 1);
Vector *v2 = check_vector(L, 2);
push_vector(L, *v1 + *v2);
return 1;
}
static int vector_unm(lua_State *L)
{
Vector *v = check_vector(L, 1);
push_vector(L, Vector(-v->x, -v->y));
return 1;
}
static int vector_sub(lua_State *L)
{
Vector *v1 = check_vector(L, 1);
Vector *v2 = check_vector(L, 2);
push_vector(L, *v1 - *v2);
return 1;
}
static int vector_eq(lua_State *L)
{
Vector *v1 = check_vector(L, 1);
Vector *v2 = check_vector(L, 2);
lua_pushboolean(L, *v1 == *v2);
return 1;
}
static int vector_dot(lua_State *L)
{
Vector *v1 = check_vector(L, 1);
Vector *v2 = check_vector(L, 2);
lua_pushnumber(L, dot(*v1, *v2));
return 1;
}
static int vector_mul(lua_State *L)
{
if (lua_type(L, 1) == LUA_TNUMBER) {
double scalar = luaL_checknumber(L, 1);
Vector *v = check_vector(L, 2);
push_vector(L, scalar * *v);
} else {
Vector *v = check_vector(L, 1);
double scalar = luaL_checknumber(L, 2);
push_vector(L, *v * scalar);
}
return 1;
}
static int vector_len(lua_State *L)
{
Vector *v = check_vector(L, 1);
lua_pushnumber(L, v->len());
return 1;
}
static int vector_sqLen(lua_State *L)
{
Vector *v = check_vector(L, 1);
lua_pushnumber(L, v->sqLen());
return 1;
}
static int vector_normalized(lua_State *L)
{
Vector *v = check_vector(L, 1);
push_vector(L, v->normalized());
return 1;
}
static int vector_orthogonal(lua_State *L)
{
Vector *v = check_vector(L, 1);
push_vector(L, v->orthogonal());
return 1;
}
static int vector_factorize(lua_State *L)
{
Vector *v = check_vector(L, 1);
Vector *unit = check_vector(L, 2);
lua_pushnumber(L, v->factorize(*unit));
return 1;
}
static int vector_angle(lua_State *L)
{
Vector *v = check_vector(L, 1);
lua_pushnumber(L, double(v->angle()));
return 1;
}
static const struct luaL_Reg vector_methods[] = {
{ "__index", vector_get },
{ "__tostring", vector_tostring },
{ "__add", vector_add },
{ "__unm", vector_unm },
{ "__sub", vector_sub },
{ "__eq", vector_eq },
{ "__mul", vector_mul },
{ "__concat", vector_dot }, // for historical reasons
{ "__pow", vector_dot },
{ "len", vector_len },
{ "sqLen", vector_sqLen },
{ "normalized", vector_normalized },
{ "orthogonal", vector_orthogonal },
{ "factorize", vector_factorize },
{ "angle", vector_angle },
{ nullptr, nullptr },
};
// --------------------------------------------------------------------
void ipelua::push_matrix(lua_State *L, const Matrix &m0)
{
Matrix *m = (Matrix *) lua_newuserdata(L, sizeof(Matrix));
luaL_getmetatable(L, "Ipe.matrix");
lua_setmetatable(L, -2);
new (m) Matrix(m0);
}
int ipelua::matrix_constructor(lua_State *L)
{
int top = lua_gettop(L);
if (top == 0)
push_matrix(L, Matrix());
else if (top == 4 || top == 6) {
double a[6];
a[4] = a[5] = 0.0;
for (int i = 0; i < top; ++i)
a[i] = luaL_checknumber(L, i+1);
push_matrix(L, Matrix(a[0], a[1], a[2], a[3], a[4], a[5]));
} else if (top == 1 && lua_type(L, 1) == LUA_TTABLE) {
double a[6];
for (int i = 0; i < 6; ++i) {
lua_rawgeti(L, 1, i+1);
a[i] = luaL_checknumber(L, -1);
lua_pop(L, 1);
}
push_matrix(L, Matrix(a[0], a[1], a[2], a[3], a[4], a[5]));
} else
luaL_error(L, "incorrect arguments for constructor");
return 1;
}
int ipelua::rotation_constructor(lua_State *L)
{
double alpha = luaL_checknumber(L, 1);
push_matrix(L, Matrix(Linear(Angle(alpha))));
return 1;
}
int ipelua::translation_constructor(lua_State *L)
{
if (lua_gettop(L) == 1) {
Vector *v = check_vector(L, 1);
push_matrix(L, Matrix(*v));
} else {
double x = luaL_checknumber(L, 1);
double y = luaL_checknumber(L, 2);
push_matrix(L, Matrix(Vector(x, y)));
}
return 1;
}
static int matrix_tostring(lua_State *L)
{
Matrix *m = check_matrix(L, 1);
lua_pushfstring(L, "[%f %f %f %f %f %f]",
m->a[0], m->a[1], m->a[2], m->a[3],
m->a[4], m->a[5]);
return 1;
}
static int matrix_eq(lua_State *L)
{
Matrix *m1 = check_matrix(L, 1);
Matrix *m2 = check_matrix(L, 2);
lua_pushboolean(L, *m1 == *m2);
return 1;
}
static int matrix_coeff(lua_State *L)
{
Matrix *m = check_matrix(L, 1);
lua_newtable(L);
for (int i = 0; i < 6; ++i) {
lua_pushnumber(L, m->a[i]);
lua_rawseti(L, -2, i+1);
}
return 1;
}
static int matrix_isIdentity(lua_State *L)
{
Matrix *m = check_matrix(L, 1);
lua_pushboolean(L, m->isIdentity());
return 1;
}
static int matrix_isSingular(lua_State *L)
{
Matrix *m = check_matrix(L, 1);
double t = m->a[0]*m->a[3]-m->a[1]*m->a[2];
lua_pushboolean(L, t == 0);
return 1;
}
static int matrix_inverse(lua_State *L)
{
Matrix *m = check_matrix(L, 1);
double t = m->a[0]*m->a[3]-m->a[1]*m->a[2];
luaL_argcheck(L, t != 0, 1, "matrix is singular");
push_matrix(L, m->inverse());
return 1;
}
static int matrix_translation(lua_State *L)
{
Matrix *m = check_matrix(L, 1);
push_vector(L, m->translation());
return 1;
}
static int matrix_linear(lua_State *L)
{
Matrix *m = check_matrix(L, 1);
push_matrix(L, Matrix(m->linear()));
return 1;
}
static int matrix_elements(lua_State *L)
{
Matrix *m = check_matrix(L, 1);
lua_createtable(L, 6, 0);
for (int i = 0; i < 6; ++i) {
lua_pushnumber(L, m->a[i]);
lua_rawseti(L, -2, i+1);
}
return 1;
}
static int matrix_mul(lua_State *L)
{
Matrix *lhs = check_matrix(L, 1);
if (is_type(L, 2, "Ipe.matrix")) {
Matrix *rhs = check_matrix(L, 2);
push_matrix(L, *lhs * *rhs);
} else if (is_type(L, 2, "Ipe.arc")) {
Arc *rhs = check_arc(L, 2);
push_arc(L, *lhs * *rhs);
} else {
Vector *v = check_vector(L, 2);
push_vector(L, *lhs * *v);
}
return 1;
}
static const struct luaL_Reg matrix_methods[] = {
{ "__tostring", matrix_tostring },
{ "__eq", matrix_eq },
{ "coeff", matrix_coeff },
{ "isIdentity", matrix_isIdentity },
{ "linear", matrix_linear },
{ "translation", matrix_translation },
{ "__mul", matrix_mul },
{ "isSingular", matrix_isSingular },
{ "inverse", matrix_inverse },
{ "elements", matrix_elements },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
void ipelua::push_rect(lua_State *L, const Rect &r0)
{
Rect *r = (Rect *) lua_newuserdata(L, sizeof(Rect));
luaL_getmetatable(L, "Ipe.rect");
lua_setmetatable(L, -2);
new (r) Rect(r0);
}
int ipelua::rect_constructor(lua_State *L)
{
push_rect(L, Rect());
return 1;
}
static int rect_tostring(lua_State *L)
{
Rect *r = check_rect(L, 1);
lua_pushfstring(L, "Rect(%f,%f,%f,%f)",
r->bottomLeft().x, r->bottomLeft().y,
r->topRight().x, r->topRight().y);
return 1;
}
static int rect_isEmpty(lua_State *L)
{
Rect *r = check_rect(L, 1);
lua_pushboolean(L, r->isEmpty());
return 1;
}
static int rect_topRight(lua_State *L)
{
Rect *r = check_rect(L, 1);
push_vector(L, r->topRight());
return 1;
}
static int rect_bottomLeft(lua_State *L)
{
Rect *r = check_rect(L, 1);
push_vector(L, r->bottomLeft());
return 1;
}
static int rect_topLeft(lua_State *L)
{
Rect *r = check_rect(L, 1);
push_vector(L, r->topLeft());
return 1;
}
static int rect_bottomRight(lua_State *L)
{
Rect *r = check_rect(L, 1);
push_vector(L, r->bottomLeft());
return 1;
}
static int rect_left(lua_State *L)
{
Rect *r = check_rect(L, 1);
lua_pushnumber(L, r->left());
return 1;
}
static int rect_right(lua_State *L)
{
Rect *r = check_rect(L, 1);
lua_pushnumber(L, r->right());
return 1;
}
static int rect_bottom(lua_State *L)
{
Rect *r = check_rect(L, 1);
lua_pushnumber(L, r->bottom());
return 1;
}
static int rect_top(lua_State *L)
{
Rect *r = check_rect(L, 1);
lua_pushnumber(L, r->top());
return 1;
}
static int rect_width(lua_State *L)
{
Rect *r = check_rect(L, 1);
lua_pushnumber(L, r->width());
return 1;
}
static int rect_height(lua_State *L)
{
Rect *r = check_rect(L, 1);
lua_pushnumber(L, r->height());
return 1;
}
static int rect_add(lua_State *L)
{
Rect *r = check_rect(L, 1);
if (is_type(L, 2, "Ipe.vector"))
r->addPoint(*check_vector(L, 2));
else
r->addRect(*check_rect(L, 2));
return 0;
}
static int rect_clipTo(lua_State *L)
{
Rect *r1 = check_rect(L, 1);
Rect *r2 = check_rect(L, 2);
r1->clipTo(*r2);
return 0;
}
static int rect_contains(lua_State *L)
{
Rect *r = check_rect(L, 1);
if (is_type(L, 2, "Ipe.vector"))
lua_pushboolean(L, r->contains(*check_vector(L, 2)));
else
lua_pushboolean(L, r->contains(*check_rect(L, 2)));
return 1;
}
static int rect_intersects(lua_State *L)
{
Rect *r1 = check_rect(L, 1);
Rect *r2 = check_rect(L, 2);
lua_pushboolean(L, r1->intersects(*r2));
return 1;
}
static const struct luaL_Reg rect_methods[] = {
{ "__tostring", rect_tostring },
{ "isEmpty", rect_isEmpty },
{ "topRight", rect_topRight },
{ "bottomLeft", rect_bottomLeft },
{ "topLeft", rect_topLeft },
{ "bottomRight", rect_bottomRight },
{ "left", rect_left },
{ "right", rect_right },
{ "bottom", rect_bottom },
{ "top", rect_top },
{ "width", rect_width },
{ "height", rect_height },
{ "add", rect_add },
{ "clipTo", rect_clipTo },
{ "contains", rect_contains },
{ "intersects", rect_intersects },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
void ipelua::push_line(lua_State *L, const Line &l0)
{
Line *l = (Line *) lua_newuserdata(L, sizeof(Line));
luaL_getmetatable(L, "Ipe.line");
lua_setmetatable(L, -2);
new (l) Line(l0);
}
int ipelua::line_constructor(lua_State *L)
{
Vector *p = check_vector(L, 1);
Vector *dir = check_vector(L, 2);
push_line(L, Line(*p, *dir));
return 1;
}
int ipelua::line_through(lua_State *L)
{
Vector *p = check_vector(L, 1);
Vector *q = check_vector(L, 2);
push_line(L, Line::through(*p, *q));
return 1;
}
int ipelua::line_bisector(lua_State *L)
{
Vector *p = check_vector(L, 1);
Vector *q = check_vector(L, 2);
luaL_argcheck(L, *p != *q, 2, "points are not distinct");
Vector mid = 0.5 * (*p + *q);
Vector dir = (*p - *q).normalized().orthogonal();
push_line(L, Line(mid, dir));
return 1;
}
static int line_tostring(lua_State *L)
{
Line *l = check_line(L, 1);
lua_pushfstring(L, "Line[(%f,%f)->(%f,%f)]",
l->iP.x, l->iP.y, l->dir().x, l->dir().y);
return 1;
}
static int line_side(lua_State *L)
{
Line *l = check_line(L, 1);
Vector *p = check_vector(L, 2);
double s = l->side(*p);
if (s > 0.0)
lua_pushnumber(L, 1.0);
else if (s < 0.0)
lua_pushnumber(L, -1.0);
else
lua_pushnumber(L, 0.0);
return 1;
}
static int line_point(lua_State *L)
{
Line *l = check_line(L, 1);
push_vector(L, l->iP);
return 1;
}
static int line_dir(lua_State *L)
{
Line *l = check_line(L, 1);
push_vector(L, l->dir());
return 1;
}
static int line_normal(lua_State *L)
{
Line *l = check_line(L, 1);
push_vector(L, l->normal());
return 1;
}
static int line_distance(lua_State *L)
{
Line *l = check_line(L, 1);
Vector *v = check_vector(L, 2);
lua_pushnumber(L, l->distance(*v));
return 1;
}
static int line_intersects(lua_State *L)
{
Line *l1 = check_line(L, 1);
Line *l2 = check_line(L, 2);
Vector pt;
if (l1->intersects(*l2, pt))
push_vector(L, pt);
else
lua_pushnil(L);
return 1;
}
static int line_project(lua_State *L)
{
Line *l = check_line(L, 1);
Vector *v = check_vector(L, 2);
push_vector(L, l->project(*v));
return 1;
}
static const struct luaL_Reg line_methods[] = {
{ "__tostring", line_tostring },
{ "side", line_side },
{ "point", line_point },
{ "dir", line_dir },
{ "normal", line_normal },
{ "distance", line_distance },
{ "intersects", line_intersects },
{ "project", line_project },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
void ipelua::push_segment(lua_State *L, const Segment &s0)
{
Segment *s = (Segment *) lua_newuserdata(L, sizeof(Segment));
luaL_getmetatable(L, "Ipe.segment");
lua_setmetatable(L, -2);
new (s) Segment(s0);
}
int ipelua::segment_constructor(lua_State *L)
{
Vector *p = check_vector(L, 1);
Vector *q = check_vector(L, 2);
push_segment(L, Segment(*p, *q));
return 1;
}
static int segment_tostring(lua_State *L)
{
Segment *s = check_segment(L, 1);
lua_pushfstring(L, "Segment[(%f,%f)-(%f,%f)]",
s->iP.x, s->iP.y, s->iQ.x, s->iQ.y);
return 1;
}
static int segment_endpoints(lua_State *L)
{
Segment *s = check_segment(L, 1);
push_vector(L, s->iP);
push_vector(L, s->iQ);
return 2;
}
static int segment_line(lua_State *L)
{
Segment *s = check_segment(L, 1);
push_line(L, s->line());
return 1;
}
static int segment_project(lua_State *L)
{
Segment *s = check_segment(L, 1);
Vector *v = check_vector(L, 2);
Vector pt;
if (s->project(*v, pt))
push_vector(L, pt);
else
lua_pushnil(L);
return 1;
}
static int segment_distance(lua_State *L)
{
Segment *s = check_segment(L, 1);
Vector *v = check_vector(L, 2);
lua_pushnumber(L, s->distance(*v));
return 1;
}
static int segment_intersects(lua_State *L)
{
Segment *s = check_segment(L, 1);
Vector pt;
if (is_type(L, 2, "Ipe.segment")) {
Segment *rhs = check_segment(L, 2);
if (s->intersects(*rhs, pt))
push_vector(L, pt);
else
lua_pushnil(L);
} else {
Line *rhs = check_line(L, 2);
if (s->intersects(*rhs, pt))
push_vector(L, pt);
else
lua_pushnil(L);
}
return 1;
}
static const struct luaL_Reg segment_methods[] = {
{ "__tostring", segment_tostring },
{ "endpoints", segment_endpoints },
{ "line", segment_line },
{ "project", segment_project },
{ "distance", segment_distance },
{ "intersects", segment_intersects },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
void ipelua::push_bezier(lua_State *L, const Bezier &b0)
{
Bezier *b = (Bezier *) lua_newuserdata(L, sizeof(Bezier));
luaL_getmetatable(L, "Ipe.bezier");
lua_setmetatable(L, -2);
new (b) Bezier(b0);
}
int ipelua::bezier_constructor(lua_State *L)
{
Vector *p[4];
for (int i = 0; i < 4; ++i)
p[i] = check_vector(L, i + 1);
push_bezier(L, Bezier(*p[0], *p[1], *p[2], *p[3]));
return 1;
}
int ipelua::quad_constructor(lua_State *L)
{
Vector *p[3];
for (int i = 0; i < 3; ++i)
p[i] = check_vector(L, i + 1);
push_bezier(L, Bezier::quadBezier(*p[0], *p[1], *p[2]));
return 1;
}
static int bezier_tostring(lua_State *L)
{
check_bezier(L, 1);
lua_pushfstring(L, "Bezier@%p", lua_topointer(L, 1));
return 1;
}
static int bezier_controlpoints(lua_State *L)
{
Bezier *b = check_bezier(L, 1);
for (int i = 0; i < 4; ++i)
push_vector(L, b->iV[i]);
return 4;
}
static int bezier_point(lua_State *L)
{
Bezier *b = check_bezier(L, 1);
double t = luaL_checknumber(L, 2);
push_vector(L, b->point(t));
return 1;
}
static int bezier_bbox(lua_State *L)
{
Bezier *b = check_bezier(L, 1);
push_rect(L, b->bbox());
return 1;
}
static int bezier_intersect(lua_State *L)
{
Bezier *b = check_bezier(L, 1);
std::vector pts;
if (is_type(L, 2, "Ipe.segment")) {
Segment *rhs = check_segment(L, 2);
b->intersect(*rhs, pts);
} else if (is_type(L, 2, "Ipe.line")) {
Line *rhs = check_line(L, 2);
b->intersect(*rhs, pts);
} else if (is_type(L, 2, "Ipe.bezier")) {
Bezier *rhs = check_bezier(L, 2);
b->intersect(*rhs, pts);
}
lua_createtable(L, pts.size(), 0);
for (int i = 0; i < int(pts.size()); ++i) {
push_vector(L, pts[i]);
lua_rawseti(L, -2, i+1);
}
return 1;
}
static int bezier_snap(lua_State *L)
{
Bezier *b = check_bezier(L, 1);
Vector *v = check_vector(L, 2);
double t;
Vector pos;
double bound = 10e9;
if (b->snap(*v, t, pos, bound)) {
lua_pushnumber(L, t);
push_vector(L, pos);
return 2;
} else
return 0;
}
static const struct luaL_Reg bezier_methods[] = {
{ "__tostring", bezier_tostring },
{ "controlpoints", bezier_controlpoints },
{ "point", bezier_point },
{ "bbox", bezier_bbox },
{ "intersect", bezier_intersect },
{ "snap", bezier_snap },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
/*
inline Arc(const Matrix &m, Angle alpha, Angle beta);
Arc(const Matrix &m0, const Vector &begp, const Vector &endp);
*/
void ipelua::push_arc(lua_State *L, const Arc &a0)
{
Arc *a = (Arc *) lua_newuserdata(L, sizeof(Arc));
luaL_getmetatable(L, "Ipe.arc");
lua_setmetatable(L, -2);
new (a) Arc(a0);
}
int ipelua::arc_constructor(lua_State *L)
{
Matrix *m = check_matrix(L, 1);
if (lua_gettop(L) == 1) {
push_arc(L, Arc(*m));
} else if (is_type(L, 2, "Ipe.vector")) {
Vector *v1 = check_vector(L, 2);
Vector *v2 = check_vector(L, 3);
push_arc(L, Arc(*m, *v1, *v2));
} else {
double alpha = luaL_checknumber(L, 2);
double beta = luaL_checknumber(L, 3);
push_arc(L, Arc(*m, Angle(alpha), Angle(beta)));
}
return 1;
}
static int arc_tostring(lua_State *L)
{
(void) check_arc(L, 1);
lua_pushfstring(L, "Arc@%p", lua_topointer(L, 1));
return 1;
}
static int arc_endpoints(lua_State *L)
{
Arc *b = check_arc(L, 1);
push_vector(L, b->beginp());
push_vector(L, b->endp());
return 2;
}
static int arc_angles(lua_State *L)
{
Arc *b = check_arc(L, 1);
lua_pushnumber(L, b->iAlpha);
lua_pushnumber(L, b->iBeta);
return 2;
}
static int arc_bbox(lua_State *L)
{
Arc *b = check_arc(L, 1);
push_rect(L, b->bbox());
return 1;
}
static int arc_matrix(lua_State *L)
{
Arc *b = check_arc(L, 1);
push_matrix(L, b->iM);
return 1;
}
static int arc_isEllipse(lua_State *L)
{
Arc *b = check_arc(L, 1);
lua_pushboolean(L, b->isEllipse());
return 1;
}
static int arc_intersect(lua_State *L)
{
Arc *a = check_arc(L, 1);
std::vector pts;
if (is_type(L, 2, "Ipe.segment")) {
Segment *rhs = check_segment(L, 2);
a->intersect(*rhs, pts);
} else if (is_type(L, 2, "Ipe.line")) {
Line *rhs = check_line(L, 2);
a->intersect(*rhs, pts);
} else if (is_type(L, 2, "Ipe.arc")) {
Arc *rhs = check_arc(L, 2);
a->intersect(*rhs, pts);
} else if (is_type(L, 2, "Ipe.bezier")) {
Bezier *rhs = check_bezier(L, 2);
a->intersect(*rhs, pts);
}
lua_createtable(L, pts.size(), 0);
for (int i = 0; i < int(pts.size()); ++i) {
push_vector(L, pts[i]);
lua_rawseti(L, -2, i+1);
}
return 1;
}
static int arc_snap(lua_State *L)
{
Arc *a = check_arc(L, 1);
Vector *v = check_vector(L, 2);
Vector pos;
Angle alpha;
(void) a->distance(*v, 10e9, pos, alpha);
lua_pushnumber(L, double(alpha));
push_vector(L, pos);
return 2;
}
static const struct luaL_Reg arc_methods[] = {
{ "__tostring", arc_tostring },
{ "endpoints", arc_endpoints },
{ "angles", arc_angles },
{ "bbox", arc_bbox },
{ "matrix", arc_matrix },
{ "isEllipse", arc_isEllipse },
{ "intersect", arc_intersect },
{ "snap", arc_snap },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
int ipelua::open_ipegeo(lua_State *L)
{
luaL_newmetatable(L, "Ipe.vector");
luaL_setfuncs(L, vector_methods, 0);
lua_pop(L, 1);
make_metatable(L, "Ipe.matrix", matrix_methods);
make_metatable(L, "Ipe.rect", rect_methods);
make_metatable(L, "Ipe.line", line_methods);
make_metatable(L, "Ipe.segment", segment_methods);
make_metatable(L, "Ipe.bezier", bezier_methods);
make_metatable(L, "Ipe.arc", arc_methods);
return 0;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelua/ipeluaobj.cpp 0000644 0001750 0001750 00000064457 13561570220 017000 0 ustar otfried otfried // --------------------------------------------------------------------
// ipeluaobj.cpp
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipelua.h"
#include "ipereference.h"
#include "ipegroup.h"
#include "ipepath.h"
#include "ipetext.h"
#include "ipeimage.h"
#include "ipeiml.h"
using namespace ipe;
using namespace ipelua;
// --------------------------------------------------------------------
static const char *const type_names[] =
{ "group", "path", "text", "image", "reference", nullptr };
static const char *const pinned_names[] =
{ "none", "horizontal", "vertical", "fixed", nullptr };
static const char *const pathmode_names[] =
{ "stroked", "strokedfilled", "filled", nullptr };
static const char *const transformation_names[] =
{ "translations", "rigid", "affine", nullptr };
static const char *const horizontal_alignment_names[] =
{ "left", "right", "hcenter", nullptr };
static const char *const vertical_alignment_names[] =
{ "bottom", "baseline", "top", "vcenter", nullptr };
const char *const ipelua::linejoin_names[] =
{ "normal", "miter", "round", "bevel", nullptr };
const char *const ipelua::linecap_names[] =
{ "normal", "butt", "round", "square", nullptr };
const char *const ipelua::fillrule_names[] =
{ "normal", "wind", "evenodd", nullptr };
// --------------------------------------------------------------------
void ipelua::push_string(lua_State *L, String str)
{
lua_pushlstring(L, str.data(), str.size());
}
void ipelua::push_object(lua_State *L, Object *s0, bool owned)
{
SObject *s = (SObject *) lua_newuserdata(L, sizeof(SObject));
s->owned = owned;
s->obj = s0;
luaL_getmetatable(L, "Ipe.object");
lua_setmetatable(L, -2);
}
void ipelua::push_color(lua_State *L, Color color)
{
lua_createtable(L, 0, 3);
lua_pushnumber(L, color.iRed.toDouble());
lua_setfield(L, -2, "r");
lua_pushnumber(L, color.iGreen.toDouble());
lua_setfield(L, -2, "g");
lua_pushnumber(L, color.iBlue.toDouble());
lua_setfield(L, -2, "b");
}
void ipelua::push_attribute(lua_State *L, Attribute att)
{
if (att.isBoolean())
lua_pushboolean(L, att.boolean());
else if (att.isSymbolic() || att.isString() || att.isEnum())
push_string(L, att.string());
else if (att.isNumber())
lua_pushnumber(L, att.number().toDouble());
else // must be color
push_color(L, att.color());
}
// i must be positive
Color ipelua::check_color(lua_State *L, int i)
{
luaL_checktype(L, i, LUA_TTABLE);
lua_getfield(L, i, "r");
lua_getfield(L, i, "g");
lua_getfield(L, i, "b");
double r = luaL_checknumber(L, -3);
double g = luaL_checknumber(L, -2);
double b = luaL_checknumber(L, -1);
lua_pop(L, 3);
Color color;
color.iRed = Fixed::fromDouble(r);
color.iGreen = Fixed::fromDouble(g);
color.iBlue = Fixed::fromDouble(b);
return color;
}
#if 0
// i must be positive
Attribute ipelua::check_attribute(lua_State *L, int i)
{
if (lua_type(L, i) == LUA_TNUMBER) {
double v = luaL_checknumber(L, i);
return Attribute(Fixed::fromInternal(int(v * 1000 + 0.5)));
} else if (lua_type(L, i) == LUA_TSTRING) {
const char *s = luaL_checklstring(L, i, nullptr);
if (!strcmp(s, "true"))
return Attribute::Boolean(true);
if (!strcmp(s, "false"))
return Attribute::Boolean(false);
if (('a' <= s[0] && s[0] <= 'z') || ('A' <= s[0] && s[0] <= 'Z'))
return Attribute(true, s);
else
return Attribute(false, s);
} else if (lua_type(L, i) == LUA_TTABLE) {
Color color = check_color(L, i);
return Attribute(color);
} else if (lua_type(L, i) == LUA_TBOOLEAN) {
return Attribute::Boolean(lua_toboolean(L, i));
} else {
luaL_argerror(L, i, "attribute expected");
return Attribute::NORMAL(); // placate compiler
}
}
#endif
// i must be positive
Attribute ipelua::check_color_attribute(lua_State *L, int i)
{
if (lua_type(L, i) == LUA_TSTRING) {
const char *s = luaL_checklstring(L, i, nullptr);
return Attribute(true, s);
} else {
Color color = check_color(L, i);
return Attribute(color);
}
}
// i must be positive
Attribute ipelua::check_bool_attribute(lua_State *L, int i)
{
static const char * const bool_names[] = { "false", "true" };
if (lua_type(L, i) == LUA_TBOOLEAN)
return Attribute::Boolean(lua_toboolean(L, i));
int val = luaL_checkoption(L, i, nullptr, bool_names);
return Attribute::Boolean(val);
}
// i must be positive
Attribute ipelua::check_number_attribute(lua_State *L, int i)
{
if (lua_type(L, i) == LUA_TNUMBER) {
double v = luaL_checknumber(L, i);
return Attribute(Fixed::fromInternal(int(v * 1000 + 0.5)));
}
const char *s = luaL_checklstring(L, i, nullptr);
return Attribute(true, s);
}
Attribute ipelua::check_property(Property prop, lua_State *L, int i)
{
int val;
switch (prop) {
case EPropHorizontalAlignment:
val = luaL_checkoption(L, i, nullptr, horizontal_alignment_names);
return Attribute(THorizontalAlignment(val));
case EPropVerticalAlignment:
val = luaL_checkoption(L, i, nullptr, vertical_alignment_names);
return Attribute(TVerticalAlignment(val));
case EPropLineJoin:
val = luaL_checkoption(L, i, nullptr, linejoin_names);
return Attribute(TLineJoin(val));
case EPropLineCap:
val = luaL_checkoption(L, i, nullptr, linecap_names);
return Attribute(TLineCap(val));
case EPropFillRule:
val = luaL_checkoption(L, i, nullptr, fillrule_names);
return Attribute(TFillRule(val));
case EPropPinned:
val = luaL_checkoption(L, i, nullptr, pinned_names);
return Attribute(TPinned(val));
case EPropTransformations:
val = luaL_checkoption(L, i, nullptr, transformation_names);
return Attribute(TTransformations(val));
case EPropPathMode:
val = luaL_checkoption(L, i, nullptr, pathmode_names);
return Attribute(TPathMode(val));
case EPropPen:
case EPropSymbolSize:
case EPropFArrowSize:
case EPropRArrowSize:
case EPropTextSize:
return check_number_attribute(L, i);
case EPropWidth: { // absolute number only!
double v = luaL_checknumber(L, i);
return Attribute(Fixed::fromInternal(int(v * 1000 + 0.5))); }
case EPropFArrowShape:
case EPropRArrowShape:
case EPropMarkShape:
case EPropTextStyle:
case EPropLabelStyle:
case EPropOpacity:
case EPropStrokeOpacity:
case EPropGradient:
case EPropDecoration:
case EPropTiling: // symbolic string only
return Attribute(true, luaL_checklstring(L, i, nullptr));
case EPropStrokeColor:
case EPropFillColor:
return check_color_attribute(L, i);
case EPropDashStyle:
return Attribute::makeDashStyle(luaL_checklstring(L, i, nullptr));
case EPropFArrow:
case EPropRArrow:
case EPropMinipage:
case EPropTransformableText:
return check_bool_attribute(L, i);
}
return Attribute::NORMAL(); // placate compiler
}
static void get_attribute(lua_State *L, int i, Property prop,
const char *key, Attribute &att)
{
lua_getfield(L, i, key);
if (!lua_isnil(L, -1))
att = check_property(prop, L, lua_gettop(L)); // arg must be positive
lua_pop(L, 1);
}
static void get_boolean(lua_State *L, int i, const char *key, bool &att)
{
lua_getfield(L, i, key);
att = lua_toboolean(L, -1);
lua_pop(L, 1);
}
static int get_option(lua_State *L, int i, const char *key,
const char *const *names)
{
lua_getfield(L, i, key);
int val;
if (!lua_isnil(L, -1))
val = luaL_checkoption(L, -1, nullptr, names);
else
val = -1;
lua_pop(L, 1);
return val;
}
// i must be positive
void ipelua::check_allattributes(lua_State *L, int i, AllAttributes &all)
{
luaL_checktype(L, i, LUA_TTABLE);
get_attribute(L, i, EPropStrokeColor, "stroke", all.iStroke);
get_attribute(L, i, EPropFillColor, "fill", all.iFill);
get_attribute(L, i, EPropDashStyle, "dashstyle", all.iDashStyle);
get_attribute(L, i, EPropPen, "pen", all.iPen);
get_boolean(L, i, "farrow", all.iFArrow);
get_boolean(L, i, "rarrow", all.iRArrow);
get_attribute(L, i, EPropFArrowShape, "farrowshape", all.iFArrowShape);
get_attribute(L, i, EPropRArrowShape, "rarrowshape", all.iRArrowShape);
get_attribute(L, i, EPropFArrowSize, "farrowsize", all.iFArrowSize);
get_attribute(L, i, EPropRArrowSize, "rarrowsize", all.iRArrowSize);
get_attribute(L, i, EPropSymbolSize, "symbolsize", all.iSymbolSize);
get_attribute(L, i, EPropMarkShape, "markshape", all.iMarkShape);
get_attribute(L, i, EPropTextSize, "textsize", all.iTextSize);
get_boolean(L, i, "transformabletext", all.iTransformableText);
get_attribute(L, i, EPropTextStyle, "textstyle", all.iTextStyle);
get_attribute(L, i, EPropTextStyle, "labelstyle", all.iLabelStyle);
get_attribute(L, i, EPropOpacity, "opacity", all.iOpacity);
get_attribute(L, i, EPropStrokeOpacity, "strokeopacity", all.iStrokeOpacity);
get_attribute(L, i, EPropTiling, "tiling", all.iTiling);
get_attribute(L, i, EPropGradient, "gradient", all.iGradient);
int t;
t = get_option(L, i, "horizontalalignment", horizontal_alignment_names);
if (t >= 0) all.iHorizontalAlignment = THorizontalAlignment(t);
t = get_option(L, i, "verticalalignment", vertical_alignment_names);
if (t >= 0) all.iVerticalAlignment = TVerticalAlignment(t);
t = get_option(L, i, "linejoin", linejoin_names);
if (t >= 0) all.iLineJoin = TLineJoin(t);
t = get_option(L, i, "linecap", linecap_names);
if (t >= 0) all.iLineCap = TLineCap(t);
t = get_option(L, i, "fillrule", fillrule_names);
if (t >= 0) all.iFillRule = TFillRule(t);
t = get_option(L, i, "pinned", pinned_names);
if (t >= 0) all.iPinned = TPinned(t);
t = get_option(L, i, "transformations", transformation_names);
if (t >= 0) all.iTransformations = TTransformations(t);
t = get_option(L, i, "pathmode", pathmode_names);
if (t >= 0) all.iPathMode = TPathMode(t);
}
// --------------------------------------------------------------------
int ipelua::reference_constructor(lua_State *L)
{
AllAttributes all;
check_allattributes(L, 1, all);
Attribute name(true, luaL_checklstring(L, 2, nullptr));
Vector *v = check_vector(L, 3);
Reference *r = new Reference(all, name, *v);
push_object(L, r);
return 1;
}
int ipelua::text_constructor(lua_State *L)
{
AllAttributes all;
check_allattributes(L, 1, all);
const char *s = luaL_checklstring(L, 2, nullptr);
Vector *v = check_vector(L, 3);
double width = 10.0;
Text::TextType type = Text::ELabel;
if (lua_isnumber(L, 4)) {
type = Text::EMinipage;
width = luaL_checknumber(L, 4);
}
Text *t = new Text(all, s, *v, type, width);
push_object(L, t);
return 1;
}
int ipelua::path_constructor(lua_State *L)
{
AllAttributes all;
check_allattributes(L, 1, all);
Shape shape = check_shape(L, 2);
bool withArrows = lua_toboolean(L, 3);
Path *p = new Path(all, shape, withArrows);
push_object(L, p);
return 1;
}
int ipelua::group_constructor(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
Group *g = new Group();
// make sure Lua will collect it if exception happens
push_object(L, g);
int no = lua_rawlen(L, 1);
for (int i = 1; i <= no; ++i) {
lua_rawgeti(L, 1, i);
luaL_argcheck(L, is_type(L, -1, "Ipe.object"), 1,
"element is not an Ipe object");
SObject *p = (SObject *) lua_touserdata(L, -1);
g->push_back(p->obj->clone());
lua_pop(L, 1); // object i
}
return 1;
}
int ipelua::xml_constructor(lua_State *L)
{
String s = luaL_checklstring(L, 1, nullptr);
Buffer buffer(s.data(), s.size());
BufferSource source(buffer);
ImlParser parser(source);
String tag = parser.parseToTag();
if (tag == "ipeselection") {
lua_newtable(L);
int index = 1;
XmlAttributes attr;
if (!parser.parseAttributes(attr))
return 0;
tag = parser.parseToTag();
while (tag == "bitmap") {
if (!parser.parseBitmap())
return false;
tag = parser.parseToTag();
}
for (;;) {
if (tag == "/ipeselection")
return 1;
Object *obj = parser.parseObject(tag);
if (!obj)
return 0;
push_object(L, obj);
lua_rawseti(L, -2, index);
++index;
tag = parser.parseToTag();
}
} else {
Object *obj = parser.parseObject(tag);
if (obj) {
push_object(L, obj);
return 1;
}
}
return 0;
}
// --------------------------------------------------------------------
static int object_destructor(lua_State *L)
{
SObject *r = check_object(L, 1);
if (r->owned && r->obj)
delete r->obj;
r->obj = nullptr;
return 0;
}
static int object_tostring(lua_State *L)
{
SObject *s = check_object(L, 1);
lua_pushfstring(L, "Object(%s)@%p",
type_names[s->obj->type()],
lua_topointer(L, 1));
return 1;
}
static int object_type(lua_State *L)
{
SObject *s = check_object(L, 1);
lua_pushstring(L, type_names[s->obj->type()]);
return 1;
}
static int object_set(lua_State *L)
{
SObject *s = check_object(L, 1);
Property prop = Property(luaL_checkoption(L, 2, nullptr, property_names));
Attribute value = check_property(prop, L, 3);
s->obj->setAttribute(prop, value);
return 0;
}
static int object_get(lua_State *L)
{
SObject *s = check_object(L, 1);
Property prop = Property(luaL_checkoption(L, 2, nullptr, property_names));
Attribute value = s->obj->getAttribute(prop);
push_attribute(L, value);
return 1;
}
static int object_position(lua_State *L)
{
Object *obj = check_object(L, 1)->obj;
luaL_argcheck(L, obj->type() == Object::EText ||
obj->type() == Object::EReference, 1,
"not a text or reference object");
if (obj->asReference()) {
push_vector(L, obj->asReference()->position());
return 1;
} else if (obj->asText()) {
push_vector(L, obj->asText()->position());
return 1;
}
return 0;
}
static int object_text(lua_State *L)
{
Object *obj = check_object(L, 1)->obj;
if (obj->type() == Object::EGroup) {
push_string(L, obj->asGroup()->url());
} else {
luaL_argcheck(L, obj->type() == Object::EText, 1, "not a text object");
push_string(L, obj->asText()->text());
}
return 1;
}
static int object_setText(lua_State *L)
{
Object *obj = check_object(L, 1)->obj;
String s = luaL_checklstring(L, 2, nullptr);
if (obj->type() == Object::EGroup) {
obj->asGroup()->setUrl(s);
} else {
luaL_argcheck(L, obj->type() == Object::EText, 1, "not a text object");
obj->asText()->setText(s);
}
return 0;
}
static int object_clone(lua_State *L)
{
SObject *s = check_object(L, 1);
push_object(L, s->obj->clone());
return 1;
}
static int object_matrix(lua_State *L)
{
SObject *s = check_object(L, 1);
push_matrix(L, s->obj->matrix());
return 1;
}
static int object_setMatrix(lua_State *L)
{
SObject *s = check_object(L, 1);
Matrix *m = check_matrix(L, 2);
s->obj->setMatrix(*m);
return 0;
}
static int object_elements(lua_State *L)
{
Object *obj = check_object(L, 1)->obj;
luaL_argcheck(L, obj->type() == Object::EGroup, 1, "not a group object");
Group *g = obj->asGroup();
lua_createtable(L, g->count(), 0);
for (int i = 0; i < g->count(); ++i) {
push_object(L, g->object(i)->clone());
lua_rawseti(L, -2, i+1);
}
return 1;
}
static int object_element(lua_State *L)
{
Object *obj = check_object(L, 1)->obj;
luaL_argcheck(L, obj->type() == Object::EGroup, 1, "not a group object");
int idx = (int) luaL_checkinteger(L, 2);
Group *g = obj->asGroup();
luaL_argcheck(L, 1 <= idx && idx <= g->count(), 2, "incorrect element index");
push_object(L, g->object(idx-1)->clone());
return 1;
}
static int object_elementType(lua_State *L)
{
Object *obj = check_object(L, 1)->obj;
luaL_argcheck(L, obj->type() == Object::EGroup, 1, "not a group object");
int idx = (int) luaL_checkinteger(L, 2);
Group *g = obj->asGroup();
luaL_argcheck(L, 1 <= idx && idx <= g->count(), 2, "incorrect element index");
lua_pushstring(L, type_names[g->object(idx-1)->type()]);
return 1;
}
static int object_xml(lua_State *L)
{
SObject *obj = check_object(L, 1);
String s;
StringStream stream(s);
obj->obj->saveAsXml(stream, String());
push_string(L, s);
return 1;
}
static int object_addToBBox(lua_State *L)
{
SObject *s = check_object(L, 1);
Rect *r = check_rect(L, 2);
Matrix *m = check_matrix(L, 3);
bool cp = true;
if (lua_type(L, 4) == LUA_TBOOLEAN)
cp = lua_toboolean(L, 4);
s->obj->addToBBox(*r, *m, cp);
return 0;
}
// --------------------------------------------------------------------
static const char * const segtype_names[] =
{ "arc", "segment", "spline", "oldspline", nullptr };
static int segtype_cp[] = { 2, 2, 0, 0 };
static const char * const subpath_names[] =
{ "curve", "ellipse", "closedspline", nullptr };
static bool collect_cp(lua_State *L, std::vector &cp)
{
for (int i = 0; ; ++i) {
lua_rawgeti(L, -1, i+1);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return true;
}
if (!is_type(L, -1, "Ipe.vector"))
return false;
Vector *v = check_vector(L, -1);
cp.push_back(*v);
lua_pop(L, 1); // cp
}
}
static SubPath *get_ellipse(lua_State *L, int index)
{
lua_rawgeti(L, -1, 1); // get matrix
if (!is_type(L, -1, "Ipe.matrix"))
luaL_error(L, "element %d has no matrix", index);
Matrix *m = check_matrix(L, -1);
lua_pop(L, 1); // matrix
return new Ellipse(*m);
}
static SubPath *get_closedspline(lua_State *L, int index)
{
std::vector cp;
if (!collect_cp(L, cp))
luaL_error(L, "non-vector control point in element %d", index);
return new ClosedSpline(cp);
}
static SubPath *get_curve(lua_State *L, int index)
{
std::unique_ptr c(new Curve());
lua_getfield(L, -1, "closed");
if (!lua_isboolean(L, -1))
luaL_error(L, "element %d has no 'closed' field", index);
c->setClosed(lua_toboolean(L, -1));
lua_pop(L, 1); // closed
for (int i = 0; ; ++i) {
lua_rawgeti(L, -1, i+1);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
if (c->countSegments() == 0)
luaL_error(L, "element %d has no segments", index);
return c.release();
}
if (!lua_istable(L, -1))
luaL_error(L, "segment %d of element %d is not a table", i+1, index);
lua_getfield(L, -1, "type");
if (!lua_isstring(L, -1))
luaL_error(L, "segment %d of element %d has no type", i+1, index);
int type = test_option(L, -1, segtype_names);
if (type < 0)
luaL_error(L, "segment %d of element %d has invalid type", i+1, index);
lua_pop(L, 1); // pop type
std::vector cp;
if (!collect_cp(L, cp))
luaL_error(L, "non-vector control point in segment %d of element %d",
i+1, index);
int cpn = segtype_cp[type];
if (int(cp.size()) < 2 || (cpn > 0 && int(cp.size()) != cpn))
luaL_error(L, "invalid # of control points in segment %d of element %d",
i+1, index);
switch (type) {
case CurveSegment::EArc: {
lua_getfield(L, -1, "arc");
if (!is_type(L, -1, "Ipe.arc"))
luaL_error(L, "segment %d of element %d has no arc", i+1, index);
Arc *a = check_arc(L, -1);
lua_pop(L, 1); // arc
c->appendArc(a->iM, cp[0], cp[1]);
break; }
case CurveSegment::ESegment:
c->appendSegment(cp[0], cp[1]);
break;
case CurveSegment::ESpline:
c->appendSpline(cp);
break;
case CurveSegment::EOldSpline:
c->appendOldSpline(cp);
break;
default:
break;
}
lua_pop(L, 1); // pop segment table
}
}
// index must be positive
Shape ipelua::check_shape(lua_State *L, int index)
{
luaL_checktype(L, index, LUA_TTABLE);
Shape shape;
for (int i = 0; ; ++i) {
lua_rawgeti(L, index, i+1);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return shape;
}
if (!lua_istable(L, -1))
luaL_error(L, "element %d is not a table", i+1);
lua_getfield(L, -1, "type");
// stack: subpath, type
if (!lua_isstring(L, -1))
luaL_error(L, "element %d has no type", i+1);
int type = test_option(L, -1, subpath_names);
lua_pop(L, 1); // type
switch (type) {
case SubPath::EEllipse:
shape.appendSubPath(get_ellipse(L, i+1));
break;
case SubPath::EClosedSpline:
shape.appendSubPath(get_closedspline(L, i+1));
break;
case SubPath::ECurve:
shape.appendSubPath(get_curve(L, i+1));
break;
default:
luaL_error(L, "element %d has invalid type", i+1);
}
lua_pop(L, 1); // subpath
}
}
static void push_segment(lua_State *L, const CurveSegment &seg)
{
lua_createtable(L, seg.countCP(), (seg.type() == CurveSegment::EArc ? 2 : 1));
lua_pushstring(L, segtype_names[seg.type()]);
lua_setfield(L, -2, "type");
for (int i = 0; i < seg.countCP(); ++i) {
push_vector(L, seg.cp(i));
lua_rawseti(L, -2, i+1);
}
if (seg.type() == CurveSegment::EArc) {
push_arc(L, seg.arc());
lua_setfield(L, -2, "arc");
}
}
static void push_subpath(lua_State *L, const SubPath *sp)
{
switch (sp->type()) {
case SubPath::EEllipse:
lua_createtable(L, 1, 1);
lua_pushstring(L, "ellipse");
lua_setfield(L, -2, "type");
push_matrix(L, sp->asEllipse()->matrix());
lua_rawseti(L, -2, 1);
break;
case SubPath::EClosedSpline: {
const ClosedSpline *cs = sp->asClosedSpline();
lua_createtable(L, cs->iCP.size(), 1);
lua_pushstring(L, "closedspline");
lua_setfield(L, -2, "type");
for (int j = 0; j < size(cs->iCP); ++j) {
push_vector(L, cs->iCP[j]);
lua_rawseti(L, -2, j+1);
}
break; }
case SubPath::ECurve: {
const Curve *c = sp->asCurve();
lua_createtable(L, c->countSegments(), 2);
lua_pushstring(L, "curve");
lua_setfield(L, -2, "type");
lua_pushboolean(L, c->closed());
lua_setfield(L, -2, "closed");
for (int j = 0; j < c->countSegments(); ++j) {
push_segment(L, c->segment(j));
lua_rawseti(L, -2, j+1);
}
break; }
}
}
static void push_shape(lua_State *L, const Shape &shape)
{
lua_createtable(L, shape.countSubPaths(), 0);
for (int i = 0; i < shape.countSubPaths(); ++i) {
push_subpath(L, shape.subPath(i));
lua_rawseti(L, -2, i+1);
}
}
static int object_shape(lua_State *L)
{
Object *s = check_object(L, 1)->obj;
luaL_argcheck(L, s->type() == Object::EPath, 1, "not a path object");
const Shape &shape = s->asPath()->shape();
push_shape(L, shape);
return 1;
}
static int object_setShape(lua_State *L)
{
Object *s = check_object(L, 1)->obj;
luaL_argcheck(L, s->type() == Object::EPath, 1, "not a path object");
Shape shape = check_shape(L, 2);
s->asPath()->setShape(shape);
return 1;
}
static int object_count(lua_State *L)
{
Object *s = check_object(L, 1)->obj;
luaL_argcheck(L, s->type() == Object::EGroup, 1, "not a group object");
lua_pushnumber(L, s->asGroup()->count());
return 1;
}
static int object_clip(lua_State *L)
{
Object *s = check_object(L, 1)->obj;
luaL_argcheck(L, s->type() == Object::EGroup, 1, "not a group object");
const Shape &shape = s->asGroup()->clip();
if (shape.countSubPaths() > 0) {
push_shape(L, shape);
return 1;
} else
return 0;
}
static int object_setclip(lua_State *L)
{
Object *s = check_object(L, 1)->obj;
luaL_argcheck(L, s->type() == Object::EGroup, 1, "not a group object");
if (lua_isnoneornil(L, 2)) {
s->asGroup()->setClip(Shape());
} else {
Shape shape = check_shape(L, 2);
s->asGroup()->setClip(shape);
}
return 0;
}
static int object_symbol(lua_State *L)
{
Object *s = check_object(L, 1)->obj;
luaL_argcheck(L, s->type() == Object::EReference, 1,
"not a reference object");
push_string(L, s->asReference()->name().string());
return 1;
}
static int object_info(lua_State *L)
{
Object *s = check_object(L, 1)->obj;
luaL_argcheck(L, s->type() == Object::EImage, 1, "not an image object");
Bitmap bm = s->asImage()->bitmap();
lua_createtable(L, 0, 7);
lua_pushnumber(L, bm.width());
lua_setfield(L, -2, "width");
lua_pushnumber(L, bm.height());
lua_setfield(L, -2, "height");
String format;
if (bm.isJpeg())
format = "jpg";
else {
if (bm.isGray())
format = "gray";
else
format = "rgb";
if (bm.hasAlpha())
format += " alpha";
else if (bm.colorKey() >= 0)
format += " colorkeyed";
}
push_string(L, format);
lua_setfield(L, -2, "format");
return 1;
}
static int object_savePixels(lua_State *L)
{
Object *s = check_object(L, 1)->obj;
luaL_argcheck(L, s->type() == Object::EImage, 1, "not an image object");
String fname = luaL_checklstring(L, 2, nullptr);
Bitmap bm = s->asImage()->bitmap();
bm.savePixels(fname.z());
return 0;
}
// --------------------------------------------------------------------
static const struct luaL_Reg object_methods[] = {
{ "__tostring", object_tostring },
{ "__gc", object_destructor },
{ "type", object_type },
{ "set", object_set },
{ "get", object_get },
{ "xml", object_xml },
{ "clone", object_clone },
{ "matrix", object_matrix },
{ "setMatrix", object_setMatrix },
{ "addToBBox", object_addToBBox },
{ "position", object_position },
{ "shape", object_shape },
{ "setShape", object_setShape },
{ "count", object_count },
{ "clip", object_clip },
{ "setClip", object_setclip },
{ "symbol", object_symbol },
{ "info", object_info },
{ "savePixels", object_savePixels },
{ "position", object_position },
{ "text", object_text },
{ "setText", object_setText },
{ "elements", object_elements },
{ "element", object_element },
{ "elementType", object_elementType },
{ nullptr, nullptr }
};
// --------------------------------------------------------------------
int ipelua::open_ipeobj(lua_State *L)
{
make_metatable(L, "Ipe.object", object_methods);
return 0;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelua/ipelua.h 0000644 0001750 0001750 00000015636 13561570220 015745 0 ustar otfried otfried // --------------------------------------------------------------------
// ipelua.h
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef IPELUA_H
#define IPELUA_H
extern "C" {
#include
#include
#include
}
#include "ipestyle.h"
#include "ipepage.h"
#include "ipeshape.h"
#include "ipedoc.h"
#include "ipelet.h"
namespace ipelua {
struct SSheet {
bool owned;
ipe::StyleSheet *sheet;
};
struct SCascade {
bool owned;
ipe::Cascade *cascade;
};
struct SPage {
bool owned;
ipe::Page *page;
};
struct SObject {
bool owned;
ipe::Object *obj;
};
inline ipe::Document **check_document(lua_State *L, int i)
{
return (ipe::Document **) luaL_checkudata(L, i, "Ipe.document");
}
inline ipe::Vector *check_vector(lua_State *L, int i)
{
return (ipe::Vector *) luaL_checkudata(L, i, "Ipe.vector");
}
inline ipe::Matrix *check_matrix(lua_State *L, int i)
{
return (ipe::Matrix *) luaL_checkudata(L, i, "Ipe.matrix");
}
inline ipe::Rect *check_rect(lua_State *L, int i)
{
return (ipe::Rect *) luaL_checkudata(L, i, "Ipe.rect");
}
inline ipe::Line *check_line(lua_State *L, int i)
{
return (ipe::Line *) luaL_checkudata(L, i, "Ipe.line");
}
inline ipe::Segment *check_segment(lua_State *L, int i)
{
return (ipe::Segment *) luaL_checkudata(L, i, "Ipe.segment");
}
inline ipe::Bezier *check_bezier(lua_State *L, int i)
{
return (ipe::Bezier *) luaL_checkudata(L, i, "Ipe.bezier");
}
inline ipe::Arc *check_arc(lua_State *L, int i)
{
return (ipe::Arc *) luaL_checkudata(L, i, "Ipe.arc");
}
inline SObject *check_object(lua_State *L, int i)
{
return (SObject *) luaL_checkudata(L, i, "Ipe.object");
}
inline SSheet *check_sheet(lua_State *L, int i)
{
return (SSheet *) luaL_checkudata(L, i, "Ipe.sheet");
}
inline SCascade *check_cascade(lua_State *L, int i)
{
return (SCascade *) luaL_checkudata(L, i, "Ipe.cascade");
}
inline SPage *check_page(lua_State *L, int i)
{
return (SPage *) luaL_checkudata(L, i, "Ipe.page");
}
inline ipe::Ipelet **check_ipelet(lua_State *L, int i)
{
return (ipe::Ipelet **) luaL_checkudata(L, i, "Ipe.ipelet");
}
inline void luacall(lua_State *L, int nargs, int nresults) {
lua_callk(L, nargs, nresults, 0, nullptr);
}
// --------------------------------------------------------------------
extern void make_metatable(lua_State *L, const char *name,
const struct luaL_Reg *methods);
extern bool is_type(lua_State *L, int ud, const char *tname);
extern const char *const linejoin_names[];
extern const char *const linecap_names[];
extern const char *const fillrule_names[];
extern ipe::String check_filename(lua_State *L, int index);
// geo
extern void push_vector(lua_State *L, const ipe::Vector &v);
extern int vector_constructor(lua_State *L);
extern int direction_constructor(lua_State *L);
extern void push_matrix(lua_State *L, const ipe::Matrix &m);
extern int matrix_constructor(lua_State *L);
extern int rotation_constructor(lua_State *L);
extern int translation_constructor(lua_State *L);
extern void push_rect(lua_State *L, const ipe::Rect &r);
extern int rect_constructor(lua_State *L);
extern void push_line(lua_State *L, const ipe::Line &l);
extern int line_constructor(lua_State *L);
extern int line_through(lua_State *L);
extern int line_bisector(lua_State *L);
extern void push_segment(lua_State *L, const ipe::Segment &s);
extern int segment_constructor(lua_State *L);
extern void push_bezier(lua_State *L, const ipe::Bezier &b);
extern int bezier_constructor(lua_State *L);
extern int quad_constructor(lua_State *L);
extern void push_arc(lua_State *L, const ipe::Arc &a);
extern int arc_constructor(lua_State *L);
// obj
extern void push_string(lua_State *L, ipe::String str);
extern void push_color(lua_State *L, ipe::Color color);
extern void push_attribute(lua_State *L, ipe::Attribute att);
extern ipe::Attribute check_color_attribute(lua_State *L, int i);
extern ipe::Attribute check_number_attribute(lua_State *L, int i);
extern ipe::Attribute check_bool_attribute(lua_State *L, int i);
extern ipe::Color check_color(lua_State *L, int i);
extern ipe::Attribute check_property(ipe::Property prop, lua_State *L, int i);
extern void check_allattributes(lua_State *L, int i, ipe::AllAttributes &all);
extern void push_object(lua_State *L, ipe::Object *obj, bool owned = true);
extern int reference_constructor(lua_State *L);
extern int text_constructor(lua_State *L);
extern int path_constructor(lua_State *L);
extern int group_constructor(lua_State *L);
extern int xml_constructor(lua_State *L);
extern ipe::Shape check_shape(lua_State *L, int index);
// style
extern void push_sheet(lua_State *L, ipe::StyleSheet *s, bool owned = true);
extern void push_cascade(lua_State *L, ipe::Cascade *s, bool owned = true);
extern int sheet_constructor(lua_State *L);
extern int cascade_constructor(lua_State *L);
extern int test_option(lua_State *L, int i, const char * const *names);
// page
extern void push_page(lua_State *L, ipe::Page *page, bool owned = true);
extern int check_layer(lua_State *L, int i, ipe::Page *p);
extern int check_viewno(lua_State *L, int i, ipe::Page *p, int extra = 0);
extern int page_constructor(lua_State *L);
// ipelet
extern int ipelet_constructor(lua_State *L);
extern void get_snap(lua_State *L, int i, ipe::Snap &snap);
// open components
extern int open_ipegeo(lua_State *L);
extern int open_ipeobj(lua_State *L);
extern int open_ipestyle(lua_State *L);
extern int open_ipepage(lua_State *L);
extern int open_ipelets(lua_State *L);
} // namespace
extern "C" int luaopen_ipe(lua_State *L);
// --------------------------------------------------------------------
#endif
ipe-7.2.13/src/iperender/ 0000755 0001750 0001750 00000000000 13561570220 015000 5 ustar otfried otfried ipe-7.2.13/src/iperender/Makefile 0000644 0001750 0001750 00000001425 13561570220 016442 0 ustar otfried otfried # --------------------------------------------------------------------
# Makefile for Iperender
# --------------------------------------------------------------------
OBJDIR = $(BUILDDIR)/obj/iperender
include ../common.mak
TARGET = $(call exe_target,iperender)
CPPFLAGS += -I../include $(CAIRO_CFLAGS) -I../ipecairo
LIBS += -L$(buildlib) -lipecairo -lipe $(CAIRO_LIBS)
all: $(TARGET)
sources = iperender.cpp
$(TARGET): $(objects)
$(MAKE_BINDIR)
$(CXX) $(LDFLAGS) -o $@ $^ $(LIBS)
clean:
@-rm -f $(objects) $(TARGET) $(DEPEND)
$(DEPEND): Makefile
$(MAKE_DEPEND)
-include $(DEPEND)
install: $(TARGET)
$(INSTALL_DIR) $(INSTALL_ROOT)$(IPEBINDIR)
$(INSTALL_PROGRAMS) $(TARGET) $(INSTALL_ROOT)$(IPEBINDIR)
# --------------------------------------------------------------------
ipe-7.2.13/src/iperender/iperender.cpp 0000644 0001750 0001750 00000011476 13561570220 017472 0 ustar otfried otfried // --------------------------------------------------------------------
// iperender
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipedoc.h"
#include "ipethumbs.h"
#include
#include
#include
using ipe::Document;
using ipe::Page;
using ipe::Thumbnail;
// --------------------------------------------------------------------
static int renderPage(Thumbnail::TargetFormat fm,
const char *src, const char *dst,
const char *pageSpec, const char *viewSpec, double zoom,
bool transparent, bool nocrop)
{
Document *doc = Document::loadWithErrorReport(src);
if (!doc)
return 1;
int pageIdx = pageSpec ? doc->findPage(pageSpec) : 0;
if (pageIdx < 0) {
fprintf(stderr, "Incorrect -page specification.\n");
delete doc;
return 1;
}
const Page *page = doc->page(pageIdx);
int viewIdx = viewSpec ? page->findView(viewSpec) : 0;
if (viewIdx < 0) {
fprintf(stderr, "Incorrect -view specification.\n");
delete doc;
return 1;
}
if (doc->runLatex(src)) {
delete doc;
return 1;
}
Thumbnail tn(doc, 0);
if (!tn.saveRender(fm, dst, page, viewIdx, zoom, transparent, nocrop))
fprintf(stderr, "Failure to render page.\n");
delete doc;
return 0;
}
// --------------------------------------------------------------------
static void usage()
{
fprintf(stderr, "Usage: iperender [ -png ");
#ifdef CAIRO_HAS_PS_SURFACE
fprintf(stderr, "| -eps ");
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
fprintf(stderr, "| -pdf ");
#endif
#ifdef CAIRO_HAS_SVG_SURFACE
fprintf(stderr, "| -svg ");
#endif
fprintf(stderr, "] "
"[ -page ] [ -view ] [ -resolution ] "
"[ -transparent ] [ -nocrop ] "
"infile outfile\n"
"Iperender saves a single page of the Ipe document in some formats.\n"
" -page : page to save (default 1).\n"
" -view : view to save (default 1).\n"
" -resolution : resolution for png format (default 72.0 ppi).\n"
" -transparent: use transparent background in png format.\n"
" -nocrop : do not crop page.\n"
" can be a page number or a page name.\n"
);
exit(1);
}
int main(int argc, char *argv[])
{
ipe::Platform::initLib(ipe::IPELIB_VERSION);
// ensure at least three arguments (handles -help as well :-)
if (argc < 4)
usage();
Thumbnail::TargetFormat fm = Thumbnail::EPNG;
if (!strcmp(argv[1], "-png"))
fm = Thumbnail::EPNG;
#ifdef CAIRO_HAS_PS_SURFACE
else if (!strcmp(argv[1], "-eps"))
fm = Thumbnail::EPS;
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
else if (!strcmp(argv[1], "-pdf"))
fm = Thumbnail::EPDF;
#endif
#ifdef CAIRO_HAS_SVG_SURFACE
else if (!strcmp(argv[1], "-svg"))
fm = Thumbnail::ESVG;
#endif
else
usage();
const char *page = nullptr;
const char *view = nullptr;
double dpi = 72.0;
bool transparent = false;
bool nocrop = false;
int i = 2;
while (i < argc - 2) {
if (!strcmp(argv[i], "-page")) {
if (i + 1 == argc)
usage();
page = argv[i+1];
i += 2;
} else if (!strcmp(argv[i], "-view")) {
if (i + 1 == argc)
usage();
view = argv[i+1];
i += 2;
} else if (!strcmp(argv[i], "-resolution")) {
if (i + 1 == argc)
usage();
dpi = ipe::Lex(ipe::String(argv[i+1])).getDouble();
i += 2;
} else if (!strcmp(argv[i], "-transparent")) {
transparent = true;
++i;
} else if (!strcmp(argv[i], "-nocrop")) {
nocrop = true;
++i;
} else
usage();
}
// remaining arguments must be two filenames
const char *src = argv[i];
const char *dst = argv[i+1];
return renderPage(fm, src, dst, page, view, dpi / 72.0,
transparent, nocrop);
}
// --------------------------------------------------------------------
ipe-7.2.13/src/macos.mak 0000644 0001750 0001750 00000002645 13561570220 014626 0 ustar otfried otfried # -*- makefile -*-
# --------------------------------------------------------------------
#
# Ipe configuration for Mac OS X
#
# --------------------------------------------------------------------
#
# Where are the dependencies?
#
# Setting to use Macports libraries:
IPEDEPS ?= /opt/local
#
# Setting to use X11 libraries:
#IPEDEPS ?= /opt/X11
#
# --------------------------------------------------------------------
#
# We build as an application bundle (a directory "Ipe.app" that
# contains Ipe and all its files).
# If you don't want this, you'll need to also set IPEPREFIX and
# all the variables in the "config.mak" file.
#
IPEBUNDLE = 1
#
# Compile support for online Latex translation? (Needs curl library)
#
#IPECURL = 1
#
# --------------------------------------------------------------------
#
PNG_CFLAGS ?= -I$(IPEDEPS)/include/libpng16
PNG_LIBS ?= -L$(IPEDEPS)/lib -lpng16
FREETYPE_CFLAGS ?= -I$(IPEDEPS)/include/freetype2 -I$(IPEDEPS)/include
FREETYPE_LIBS ?= -L$(IPEDEPS)/lib -lfreetype
CAIRO_CFLAGS ?= -I$(IPEDEPS)/include/cairo
CAIRO_LIBS ?= -L$(IPEDEPS)/lib -lcairo
LUA_CFLAGS ?= -I$(IPEDEPS)/include/lua
LUA_LIBS ?= -L$(IPEDEPS)/lib -llua53 -lm
ifdef IPECURL
CURL_CFLAGS ?= -DCURL_STATICLIB -I$(IPEDEPS)/include
CURL_LIBS ?= -L$(IPEDEPS)/lib -lcurl -lz -framework Security
endif
#
IPEVERS = 7.2.13
#
CXX = clang++
#
# --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ 0000755 0001750 0001750 00000000000 13561570220 014267 5 ustar otfried otfried ipe-7.2.13/src/ipelib/ipetext.cpp 0000644 0001750 0001750 00000040365 13561570220 016465 0 ustar otfried otfried // --------------------------------------------------------------------
// The Text object.
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipetext.h"
#include "ipepainter.h"
using namespace ipe;
/*! \class ipe::Text
\ingroup obj
\brief The text object.
The text object stores a Latex source representation, which needs to
be translated into PDF by Pdflatex before it can be saved as PDF.
There are two types of text objects: labels and minipages. The
textType() method tells you which, or use isMinipage().
The dimensions of a text object are given by width(), height(), and
depth(). They are recomputed by Ipe when running LaTeX, with the
exception of the width for minipage objects (whose width is fixed).
Before Latex has been run, the dimensions are not reliable.
The position of the reference point relative to the text box is
given by verticalAlignment() and horizontalAlignment().
The text() must be a legal LaTeX fragment that can be interpreted by
LaTeX inside \c \\hbox, possibly using the macros or packages
defined in the preamble.
*/
//! Construct an empty internal text object.
Text::Text() : Object()
{
iXForm = nullptr;
iPos = Vector::ZERO;
iType = TextType(0);
iStroke = Attribute::BLACK();
iOpacity = Attribute::OPAQUE();
iStyle = Attribute::NORMAL();
iWidth = 10.0;
iHeight = 10.0;
iDepth = 0.0;
iVerticalAlignment = EAlignBottom;
iHorizontalAlignment = EAlignLeft;
}
//! Create text object.
Text::Text(const AllAttributes &attr, String data,
const Vector &pos, TextType type, double width)
: Object(attr)
{
iXForm = nullptr;
iText = data;
iStroke = attr.iStroke;
iOpacity = attr.iOpacity;
iSize = attr.iTextSize;
iPos = pos;
iType = type;
iWidth = width;
iHeight = 10.0;
iDepth = 0.0;
if (iType == ELabel) {
iStyle = attr.iLabelStyle;
iVerticalAlignment = attr.iVerticalAlignment;
} else {
iStyle = attr.iTextStyle;
iVerticalAlignment = EAlignTop;
}
iHorizontalAlignment = attr.iHorizontalAlignment;
if (!attr.iTransformableText)
// override setting
iTransformations = ETransformationsTranslations;
}
//! Copy constructor.
Text::Text(const Text &rhs)
: Object(rhs)
{
iPos = rhs.iPos;
iText = rhs.iText;
iStroke = rhs.iStroke;
iOpacity = rhs.iOpacity;
iSize = rhs.iSize;
iStyle = rhs.iStyle;
iWidth = rhs.iWidth;
iHeight = rhs.iHeight;
iDepth = rhs.iDepth;
iType = rhs.iType;
iVerticalAlignment = rhs.iVerticalAlignment;
iHorizontalAlignment = rhs.iHorizontalAlignment;
iXForm = rhs.iXForm;
if (iXForm) iXForm->iRefCount++;
}
//! Destructor.
Text::~Text()
{
if (iXForm && --iXForm->iRefCount == 0)
delete iXForm;
}
// --------------------------------------------------------------------
//! Create from XML stream.
Text::Text(const XmlAttributes &attr, String data)
: Object(attr)
{
iXForm = nullptr;
iText = data;
iStroke = Attribute::makeColor(attr["stroke"], Attribute::BLACK());
Lex st(attr["pos"]);
st >> iPos.x >> iPos.y;
iSize = Attribute::makeTextSize(attr["size"]);
String str;
iType = ELabel;
iWidth = 10.0;
if (attr.has("type", str)) {
if (str == "minipage")
iType = EMinipage;
} else if (attr.has("width", str))
iType = EMinipage; // no type attribute
if (attr.has("width", str))
iWidth = Lex(str).getDouble();
iHeight = 10.0;
if (attr.has("height", str))
iHeight = Lex(str).getDouble();
iDepth = 0.0;
if (attr.has("depth", str))
iDepth = Lex(str).getDouble();
iVerticalAlignment =
makeVAlign(attr["valign"], isMinipage() ? EAlignTop : EAlignBottom);
iHorizontalAlignment = makeHAlign(attr["halign"], EAlignLeft);
if (attr.has("style", str) && str != "normal")
iStyle = Attribute(true, str);
else
iStyle = Attribute::NORMAL();
if (attr.has("opacity", str))
iOpacity = Attribute(true, str);
else
iOpacity = Attribute::OPAQUE();
if (iType == ELabel && iStyle == Attribute::NORMAL() &&
iText.size() >= 3 && iText[0] == '$' && iText[iText.size()-1] == '$') {
for (int i = 1; i < iText.size() - 1; ++i) // check if no other $ in text
if (iText[i] == '$')
return;
iStyle = Attribute(true, "math");
iText = iText.substr(1, iText.size() - 2);
}
}
// --------------------------------------------------------------------
//! Clone object
Object *Text::clone() const
{
return new Text(*this);
}
//! Return pointer to this object.
Text *Text::asText()
{
return this;
}
Object::Type Text::type() const
{
return EText;
}
// --------------------------------------------------------------------
//! Return vertical alignment indicated by a name, or else default.
TVerticalAlignment Text::makeVAlign(String str, TVerticalAlignment def)
{
if (str == "top")
return EAlignTop;
else if (str == "bottom")
return EAlignBottom;
else if (str == "baseline")
return EAlignBaseline;
else if (str == "center")
return EAlignVCenter;
else
return def;
}
//! Return horizontal alignment indicated by a name, or else default.
THorizontalAlignment Text::makeHAlign(String str, THorizontalAlignment def)
{
if (str == "left")
return EAlignLeft;
else if (str == "right")
return EAlignRight;
else if (str == "center")
return EAlignHCenter;
else
return def;
}
//! Call visitText of visitor.
void Text::accept(Visitor &visitor) const
{
visitor.visitText(this);
}
//! Return type of text object.
Text::TextType Text::textType() const
{
if (iType == 0) // internal for title
return ELabel;
return iType;
}
//! Save object to XML stream.
void Text::saveAsXml(Stream &stream, String layer) const
{
stream << "";
stream.putXmlString(iText);
stream << "\n";
}
void Text::saveAlignment(Stream &stream, THorizontalAlignment h,
TVerticalAlignment v)
{
switch (h) {
case EAlignLeft:
break;
case EAlignHCenter:
stream << " halign=\"center\"";
break;
case EAlignRight:
stream << " halign=\"right\"";
break;
}
switch (v) {
case EAlignTop:
stream << " valign=\"top\"";
break;
case EAlignBottom:
stream << " valign=\"bottom\"";
break;
case EAlignBaseline:
stream << " valign=\"baseline\"";
break;
case EAlignVCenter:
stream << " valign=\"center\"";
break;
}
}
//! Save text as PDF.
void Text::draw(Painter &painter) const
{
painter.push();
painter.pushMatrix();
painter.transform(matrix());
painter.translate(iPos);
painter.untransform(transformations());
painter.setStroke(iStroke);
painter.setOpacity(iOpacity);
// Adjust alignment: make lower left corner of text box the origin
painter.translate(-align());
painter.drawText(this);
painter.popMatrix();
painter.pop();
}
void Text::drawSimple(Painter &painter) const
{
painter.pushMatrix();
painter.transform(matrix());
painter.translate(iPos);
painter.untransform(transformations());
painter.newPath();
double wid = iWidth;
double ht = totalHeight();
Vector offset = -align();
painter.moveTo(offset + Vector(0, 0));
painter.lineTo(offset + Vector(wid, 0));
painter.lineTo(offset + Vector(wid, ht));
painter.lineTo(offset + Vector(0, ht));
painter.closePath();
painter.drawPath(EStrokedOnly);
painter.popMatrix();
}
double Text::distance(const Vector &v, const Matrix &m,
double bound) const
{
Vector u[5];
quadrilateral(m, u);
u[4] = u[0];
double d = bound;
double d1;
for (int i = 0; i < 4; ++i) {
if ((d1 = Segment(u[i], u[i+1]).distance(v, d)) < d)
d = d1;
}
return d1;
}
void Text::addToBBox(Rect &box, const Matrix &m, bool) const
{
Vector v[4];
quadrilateral(m, v);
for (int i = 0; i < 4; ++i)
box.addPoint(v[i]);
}
void Text::snapCtl(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
(m * (matrix() * iPos)).snap(mouse, pos, bound);
Vector v[4];
quadrilateral(m, v);
for (int i = 0; i < 4; ++i)
v[i].snap(mouse, pos, bound);
}
// --------------------------------------------------------------------
//! Set stroke color
void Text::setStroke(Attribute stroke)
{
iStroke = stroke;
}
//! Set opacity of the object.
void Text::setOpacity(Attribute opaq)
{
iOpacity = opaq;
}
//! Set width of paragraph.
/*! This invalidates (and destroys) the XForm.
The function panics if object is not a (true) minipage. */
void Text::setWidth(double width)
{
assert(textType() == EMinipage);
iWidth = width;
setXForm(nullptr);
}
//! Set font size of text.
/*! This invalidates (and destroys) the XForm. */
void Text::setSize(Attribute size)
{
iSize = size;
setXForm(nullptr);
}
//! Set Latex style of text.
/*! This invalidates (and destroys) the XForm. */
void Text::setStyle(Attribute style)
{
iStyle = style;
setXForm(nullptr);
}
//! Sets the text of the text object.
/*! This invalidates (and destroys) the XForm. */
void Text::setText(String text)
{
iText = text;
setXForm(nullptr);
}
//! Change type.
/*! This invalidates (and destroys) the XForm. */
void Text::setTextType(TextType type)
{
if (type != iType) {
iType = type;
iStyle = Attribute::NORMAL();
setXForm(nullptr);
}
}
//! Change horizontal alignment (text moves with respect to reference point).
void Text::setHorizontalAlignment(THorizontalAlignment align)
{
iHorizontalAlignment = align;
}
//! Change vertical alignment (text moves with respect to reference point).
void Text::setVerticalAlignment(TVerticalAlignment align)
{
iVerticalAlignment = align;
}
bool Text::setAttribute(Property prop, Attribute value)
{
switch (prop) {
case EPropStrokeColor:
if (value != stroke()) {
setStroke(value);
return true;
}
break;
case EPropTextSize:
if (value != size()) {
setSize(value);
return true;
}
break;
case EPropTextStyle:
case EPropLabelStyle:
if ((isMinipage() != (prop == EPropTextStyle)) || value == style())
return false;
setStyle(value);
return true;
case EPropOpacity:
if (value != opacity()) {
setOpacity(value);
return true;
}
break;
case EPropHorizontalAlignment:
assert(value.isEnum());
if (value.horizontalAlignment() != horizontalAlignment()) {
iHorizontalAlignment = THorizontalAlignment(value.horizontalAlignment());
return true;
}
break;
case EPropVerticalAlignment:
assert(value.isEnum());
if (value.verticalAlignment() != verticalAlignment()) {
iVerticalAlignment = TVerticalAlignment(value.verticalAlignment());
return true;
}
break;
case EPropMinipage:
assert(value.isEnum());
if (value.boolean() != isMinipage()) {
iType = value.boolean() ? EMinipage : ELabel;
iStyle = Attribute::NORMAL();
return true;
}
break;
case EPropWidth:
assert(value.isNumber());
if (value.number().toDouble() != width()) {
setWidth(value.number().toDouble());
return true;
}
break;
case EPropTransformableText:
assert(value.isEnum());
if (value.boolean() && transformations() != ETransformationsAffine) {
setTransformations(ETransformationsAffine);
return true;
} else if (!value.boolean() &&
transformations() != ETransformationsTranslations) {
setTransformations(ETransformationsTranslations);
return true;
}
break;
default:
return Object::setAttribute(prop, value);
}
return false;
}
Attribute Text::getAttribute(Property prop) const noexcept
{
switch (prop) {
case EPropStrokeColor:
return stroke();
case EPropTextSize:
return size();
case EPropTextStyle:
case EPropLabelStyle:
return style();
case EPropOpacity:
return opacity();
case EPropHorizontalAlignment:
return Attribute(horizontalAlignment());
case EPropVerticalAlignment:
return Attribute(verticalAlignment());
case EPropMinipage:
return Attribute::Boolean(isMinipage());
case EPropWidth:
return Attribute(Fixed::fromDouble(width()));
default:
return Object::getAttribute(prop);
}
}
// --------------------------------------------------------------------
//! Check symbolic size attribute.
void Text::checkStyle(const Cascade *sheet, AttributeSeq &seq) const
{
checkSymbol(EColor, iStroke, sheet, seq);
checkSymbol(ETextSize, iSize, sheet, seq);
checkSymbol((iType == ELabel ? ELabelStyle : ETextStyle), iStyle, sheet, seq);
checkSymbol(EOpacity, iOpacity, sheet, seq);
}
//! Return quadrilateral including the text.
/*! This is the bounding box, correctly transformed by matrix(),
taking into consideration whether the object is transformable.
*/
void Text::quadrilateral(const Matrix &m, Vector v[4]) const
{
double wid = iWidth;
double ht = totalHeight();
Vector offset = -align();
v[0] = offset + Vector(0, 0);
v[1] = offset + Vector(wid, 0);
v[2] = offset + Vector(wid, ht);
v[3] = offset + Vector(0, ht);
Matrix m1 = m * matrix() * Matrix(iPos);
if (iTransformations == ETransformationsTranslations) {
m1 = Matrix(m1.translation());
} else if (iTransformations == ETransformationsRigidMotions) {
Angle alpha = Vector(m1.a[0], m1.a[1]).angle();
// ensure that (1,0) is rotated into this orientation
m1 = Matrix(m1.translation()) * Linear(alpha);
}
for (int i = 0; i < 4; ++i)
v[i] = m1 * v[i];
}
//! Update the PDF code for this object.
void Text::setXForm(XForm *xform) const
{
if (iXForm && --iXForm->iRefCount == 0)
delete iXForm;
iXForm = xform;
if (iXForm) {
iXForm->iRefCount = 1;
iDepth = iXForm->iStretch * iXForm->iDepth / 100.0;
iHeight = iXForm->iStretch * iXForm->iBBox.height() - iDepth;
if (!isMinipage())
iWidth = iXForm->iStretch * iXForm->iBBox.width();
}
}
//! Return position of reference point in text box coordinate system.
/*! Assume a coordinate system where the text box has corners (0,0)
and (Width(), TotalHeight()). This function returns the coordinates
of the reference point in this coordinate system. */
Vector Text::align() const
{
Vector align(0.0, 0.0);
switch (verticalAlignment()) {
case EAlignTop:
align.y = totalHeight();
break;
case EAlignBottom:
break;
case EAlignVCenter:
align.y = 0.5 * totalHeight();
break;
case EAlignBaseline:
align.y = depth();
break;
}
if (!isMinipage()) {
switch (horizontalAlignment()) {
case EAlignLeft:
break;
case EAlignRight:
align.x = width();
break;
case EAlignHCenter:
align.x = 0.5 * width();
break;
}
}
return align;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipefactory.cpp 0000644 0001750 0001750 00000004344 13561570220 017145 0 ustar otfried otfried // --------------------------------------------------------------------
// The Ipe object factory.
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipefactory.h"
#include "ipepath.h"
#include "ipetext.h"
#include "ipeimage.h"
#include "ipereference.h"
// --------------------------------------------------------------------
using namespace ipe;
/*! \class ipe::ObjectFactory
\ingroup high
\brief Factory for Ipe leaf objects.
*/
//! Create an Ipe object by calling the right constructor.
Object *ObjectFactory::createObject(String name, const XmlAttributes &attr,
String data)
{
if (name == "path")
return Path::create(attr, data);
else if (name == "text")
return new Text(attr, data);
else if (name == "image")
return new Image(attr, data);
else if (name == "use")
return new Reference(attr, data);
else
return nullptr;
}
//! Create an Image with a given bitmap.
Object *ObjectFactory::createImage(String /*name*/, const XmlAttributes &attr,
Bitmap bitmap)
{
return new Image(attr, bitmap);
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipegeo.cpp 0000644 0001750 0001750 00000103106 13561570220 016244 0 ustar otfried otfried // --------------------------------------------------------------------
// Ipe geometry primitives
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*! \defgroup geo Ipe Geometry
\brief Geometric primitives for Ipe.
The IpeGeo module provides a few classes for constant-size geometric
primitives, such as vector, axis-aligned rectangles, lines, rays,
line segments, etc.
*/
const double BEZIER_INTERSECT_PRECISION = 1.0;
#include "ipegeo.h"
using namespace ipe;
inline double sq(double x)
{
return x * x;
}
// --------------------------------------------------------------------
/*! \class ipe::Angle
\ingroup geo
\brief A double that's an angle.
An Angle is really nothing more than a double. Having a separate
type is sometimes useful, for instance in the Vector constructor,
and this class serves as the right place for a few utility
functions. It also makes it clear whether a value is in radians or
in degrees.
*/
double Angle::degrees() const
{
return (iAlpha / IpePi * 180.0);
}
//! Normalize the value to the range lowlimit .. lowlimit + 2 pi.
/*! This Angle object is modified, a copy is returned. */
Angle Angle::normalize(double lowlimit)
{
while (iAlpha >= lowlimit + IpeTwoPi)
iAlpha -= IpeTwoPi;
while (iAlpha < lowlimit)
iAlpha += IpeTwoPi;
return *this;
}
/*! When considering the positively oriented circle arc from angle \a
small to \a large, does it cover this angle?
*/
bool Angle::liesBetween(Angle small, Angle large) const
{
large.normalize(iAlpha);
small.normalize(large.iAlpha - IpeTwoPi);
return (iAlpha >= small.iAlpha);
}
// --------------------------------------------------------------------
/*! \class ipe::Vector
\ingroup geo
\brief Two-dimensional vector.
Unlike some other libraries, I don't make a difference between
points and vectors.
*/
//! Construct a unit vector with this direction.
Vector::Vector(Angle alpha)
{
x = cos(alpha);
y = sin(alpha);
}
//! Return angle of the vector (with positive x-direction).
/*! The returned angle lies between -pi and +pi.
Returns zero for the zero vector. */
Angle Vector::angle() const
{
if (x == 0.0 && y == 0.0)
return Angle(0.0);
else
return Angle(atan2(y, x));
}
//! The origin (zero vector).
Vector Vector::ZERO = Vector(0.0, 0.0);
double Vector::len() const
{
return sqrt(sqLen());
}
//! Return this vector normalized (with length one).
/*! Normalizing the zero vector returns the vector (1,0). */
Vector Vector::normalized() const
{
double len = sqLen();
if (len == 1.0)
return *this;
if (len == 0.0)
return Vector(1,0);
return (1.0/sqrt(len)) * (*this);
}
//! Return this vector turned 90 degrees to the left.
Vector Vector::orthogonal() const
{
return Vector(-y, x);
}
/*! Normalizes this vector into \a unit and returns length.
If this is the zero vector, \a unit is set to (1,0). */
double Vector::factorize(Vector &unit) const
{
double len = sqLen();
if (len == 0.0) {
unit = Vector(1,0);
return len;
}
if (len == 1.0) {
unit = *this;
return len;
}
len = sqrt(len);
unit = (1.0 / len) * (*this);
return len;
}
//! Snap to nearby vertex.
/*! If distance between \a mouse and this vector is less than \a
bound, set \a pos to this vector and \a bound to the distance, and
return \c true. */
bool Vector::snap(const Vector &mouse, Vector &pos,
double &bound) const
{
double d = (mouse - *this).len();
if (d < bound) {
pos = *this;
bound = d;
return true;
}
return false;
}
//! Output operator for Vector.
Stream &ipe::operator<<(Stream &stream, const Vector &rhs)
{
return stream << rhs.x << " " << rhs.y;
}
// --------------------------------------------------------------------
/*! \class ipe::Rect
\ingroup geo
\brief Axis-parallel rectangle (which can be empty)
*/
//! Create rectangle containing points \a c1 and \a c2.
Rect::Rect(const Vector &c1, const Vector &c2)
: iMin(1,0), iMax(-1,0)
{
addPoint(c1);
addPoint(c2);
}
//! Does (closed) rectangle contain the point?
bool Rect::contains(const Vector &rhs) const
{
// this correctly handles empty this
return (iMin.x <= rhs.x && rhs.x <= iMax.x &&
iMin.y <= rhs.y && rhs.y <= iMax.y);
}
//! Does rectangle contain other rectangle?
bool Rect::contains(const Rect &rhs) const
{
if (rhs.isEmpty()) return true;
if (isEmpty()) return false;
return (iMin.x <= rhs.iMin.x &&
rhs.iMax.x <= iMax.x &&
iMin.y <= rhs.iMin.y &&
rhs.iMax.y <= iMax.y);
}
//! Does rectangle intersect other rectangle?
bool Rect::intersects(const Rect &rhs) const
{
if (isEmpty() || rhs.isEmpty()) return false;
return (iMin.x <= rhs.iMax.x && rhs.iMin.x <= iMax.x &&
iMin.y <= rhs.iMax.y && rhs.iMin.y <= iMax.y);
}
//! Enlarge rectangle to contain point.
void Rect::addPoint(const Vector &rhs)
{
if (isEmpty()) {
iMin = rhs; iMax = rhs;
} else {
if (rhs.x > iMax.x)
iMax.x = rhs.x;
else if (rhs.x < iMin.x)
iMin.x = rhs.x;
if (rhs.y > iMax.y)
iMax.y = rhs.y;
else if (rhs.y < iMin.y)
iMin.y = rhs.y;
}
}
//! Enlarge rectangle to contain rhs rectangle.
/*! Does nothing if \a rhs is empty. */
void Rect::addRect(const Rect &rhs)
{
if (isEmpty()) {
iMin = rhs.iMin; iMax = rhs.iMax;
} else if (!rhs.isEmpty()) {
if (rhs.iMax.x > iMax.x)
iMax.x = rhs.iMax.x;
if (rhs.iMin.x < iMin.x)
iMin.x = rhs.iMin.x;
if (rhs.iMax.y > iMax.y)
iMax.y = rhs.iMax.y;
if (rhs.iMin.y < iMin.y)
iMin.y = rhs.iMin.y;
}
}
//! Clip rectangle to fit inside \a cbox.
/*! Does nothing if either rectangle is empty. */
void Rect::clipTo(const Rect &cbox)
{
if (isEmpty() || cbox.isEmpty())
return;
if (!intersects(cbox)) { // make box empty
iMin = Vector(1, 0);
iMax = Vector(-1, 0);
} else {
if (iMin.x < cbox.iMin.x)
iMin.x = cbox.iMin.x;
if (iMin.y < cbox.iMin.y)
iMin.y = cbox.iMin.y;
if (iMax.x > cbox.iMax.x)
iMax.x = cbox.iMax.x;
if (iMax.y > cbox.iMax.y)
iMax.y = cbox.iMax.y;
}
}
/*! Returns false if the distance between the box and v is smaller
than \a bound. Often returns true if their distance is larger than
\a bound.
*/
bool Rect::certainClearance(const Vector &v, double bound) const
{
return ((iMin.x - v.x) >= bound ||
(v.x - iMax.x) >= bound ||
(iMin.y - v.y) >= bound ||
(v.y - iMax.y) >= bound);
}
//! Output operator for Rect.
Stream &ipe::operator<<(Stream &stream, const Rect &rhs)
{
return stream << rhs.bottomLeft() << " " << rhs.topRight();
}
// --------------------------------------------------------------------
/*! \class ipe::Line
\ingroup geo
\brief A directed line.
*/
//! Construct a line from \a p with direction \a dir.
/*! Asserts unit length of \a dir. */
Line::Line(const Vector &p, const Vector &dir)
{
assert(sq(dir.sqLen() - 1.0) < 1e-10);
iP = p;
iDir = dir;
}
//! Construct a line through two points.
Line Line::through(const Vector &p, const Vector &q)
{
assert(q != p);
return Line(p, (q - p).normalized());
}
//! Result is > 0, = 0, < 0 if point lies to the left, on, to the right.
double Line::side(const Vector &p) const
{
return dot(normal(), p - iP);
}
//! Returns distance between line and \a v.
double Line::distance(const Vector &v) const
{
Vector diff = v - iP;
return (diff - dot(diff, iDir) * iDir).len();
}
inline double cross(const Vector &v1, const Vector &v2)
{
return v1.x * v2.y - v1.y * v2.x;
}
static bool line_intersection(double &lambda, const Line &l, const Line &m)
{
double denom = cross(m.dir(), l.dir());
if (denom == 0.0)
return false;
lambda = cross(l.iP - m.iP, m.dir()) / denom;
return true;
}
//! Does this line intersect \a line? If so, computes intersection point.
bool Line::intersects(const Line &line, Vector &pt)
{
double lambda;
if (line_intersection(lambda, *this, line)) {
pt = iP + lambda * iDir;
return true;
}
return false;
}
//! Orthogonally project point \a v onto the line.
Vector Line::project(const Vector &v) const
{
double dx = dot(iDir, v - iP);
return iP + dx * iDir;
}
// --------------------------------------------------------------------
/*! \class ipe::Segment
\ingroup geo
\brief A directed line segment.
*/
/*! Returns distance between segment and point \a v,
but may just return \a bound when its larger than \a bound. */
double Segment::distance(const Vector &v, double bound) const
{
if (Rect(iP, iQ).certainClearance(v, bound))
return bound;
return distance(v);
}
/*! Returns distance between segment and point \a v */
double Segment::distance(const Vector &v) const
{
Vector dir = iQ - iP;
Vector udir;
double len = dir.factorize(udir);
double dx = dot(udir, v - iP);
if (dx <= 0)
return (v - iP).len();
if (dx >= len)
return (v - iQ).len();
return (v - (iP + dx * udir)).len();
}
/*! Project point \a v orthogonally on segment.
Returns false if the point falls outside the segment. */
bool Segment::project(const Vector &v, Vector &projection) const
{
Vector dir = iQ - iP;
Vector udir;
double len = dir.factorize(udir);
double dx = dot(udir, v - iP);
if (dx <= 0 || dx >= len)
return false;
projection = iP + dx * udir;
return true;
}
//! Compute intersection point. Return \c false if segs don't intersect.
bool Segment::intersects(const Segment &seg, Vector &pt) const
{
if (iP == iQ || seg.iP == seg.iQ)
return false;
if (!Rect(iP, iQ).intersects(Rect(seg.iP, seg.iQ)))
return false;
if (!line().intersects(seg.line(), pt))
return false;
// have intersection point, check whether it's on both segments.
Vector dir = iQ - iP;
Vector dir1 = seg.iQ - seg.iP;
return (dot(pt - iP, dir) >= 0 && dot(pt - iQ, dir) <= 0 &&
dot(pt - seg.iP, dir1) >= 0 && dot(pt - seg.iQ, dir1) <= 0);
}
//! Compute intersection point. Return \c false if no intersection.
bool Segment::intersects(const Line &l, Vector &pt) const
{
if (!line().intersects(l, pt))
return false;
// have intersection point, check whether it's on the segment
Vector dir = iQ - iP;
return (dot(pt - iP, dir) >= 0 && dot(pt - iQ, dir) <= 0);
}
//! Snap mouse position to this segment.
/*! If distance between \a mouse and the segment is less than
\a bound, then set \a pos to the point on the segment,
\a bound to the distance, and return true. */
bool Segment::snap(const Vector &mouse, Vector &pos,
double &bound) const
{
if (Rect(iP, iQ).certainClearance(mouse, bound))
return false;
Vector v;
if (project(mouse, v)) {
double d = (mouse - v).len();
if (d < bound) {
pos = v;
bound = d;
return true;
}
return false;
} else
return iQ.snap(mouse, pos, bound);
}
// --------------------------------------------------------------------
/*! \class ipe::Linear
\ingroup geo
\brief Linear transformation in the plane (2x2 matrix).
*/
//! Create matrix representing a rotation by angle.
Linear::Linear(Angle angle)
{
a[0] = cos(angle);
a[1] = sin(angle);
a[2] = -a[1];
a[3] = a[0];
}
//! Parse string.
Linear::Linear(String str)
{
Lex lex(str);
lex >> a[0] >> a[1] >> a[2] >> a[3];
}
//! Return inverse.
Linear Linear::inverse() const
{
double t = determinant();
assert(t != 0);
t = 1.0/t;
return Linear(a[3]*t, -a[1]*t, -a[2]*t, a[0]*t);
}
//! Output operator for Linear.
Stream &ipe::operator<<(Stream &stream, const Linear &rhs)
{
return stream << rhs.a[0] << " " << rhs.a[1] << " "
<< rhs.a[2] << " " << rhs.a[3];
}
// --------------------------------------------------------------------
/*! \class ipe::Matrix
\ingroup geo
\brief Homogeneous transformation in the plane.
*/
//! Parse string.
Matrix::Matrix(String str)
{
Lex lex(str);
lex >> a[0] >> a[1] >> a[2] >> a[3] >> a[4] >> a[5];
}
//! Return inverse.
Matrix Matrix::inverse() const
{
double t = determinant();
assert(t != 0);
t = 1.0/t;
return Matrix(a[3]*t, -a[1]*t, -a[2]*t, a[0]*t,
(a[2]*a[5]-a[3]*a[4])*t, -(a[0]*a[5]-a[1]*a[4])*t);
}
//! Output operator for Matrix.
Stream &ipe::operator<<(Stream &stream, const Matrix &rhs)
{
return stream << rhs.a[0] << " " << rhs.a[1] << " " << rhs.a[2] << " "
<< rhs.a[3] << " " << rhs.a[4] << " " << rhs.a[5];
}
// --------------------------------------------------------------------
/*! \class ipe::Bezier
\ingroup geo
\brief A cubic Bezier spline.
*/
inline Vector midpoint(const Vector& p, const Vector& q)
{
return 0.5 * (p + q);
}
inline Vector thirdpoint(const Vector& p, const Vector& q)
{
return (1.0/3.0) * ((2 * p) + q);
}
//! Return point on curve with parameter \a t (from 0.0 to 1.0).
Vector Bezier::point(double t) const
{
double t1 = 1.0 - t;
return t1 * t1 * t1 * iV[0] + 3 * t * t1 * t1 * iV[1] +
3 * t * t * t1 * iV[2] + t * t * t * iV[3];
}
//! Return tangent direction of curve at parameter \a t (from 0.0 to 1.0).
/*! The returned vector is not normalized. */
Vector Bezier::tangent(double t) const
{
double tt = 1.0 - t;
Vector p = tt * iV[0] + t * iV[1];
Vector q = tt * iV[1] + t * iV[2];
Vector r = tt * iV[2] + t * iV[3];
p = tt * p + t * q;
q = tt * q + t * r;
r = tt * p + t * q;
return r - p;
}
/*! Returns true if the Bezier curve is nearly identical to the line
segment iV[0]..iV[3]. */
bool Bezier::straight(double precision) const
{
if (iV[0] == iV[3]) {
return ((iV[1] - iV[0]).len() < precision &&
(iV[2] - iV[0]).len() < precision);
} else {
Line l = Line::through(iV[0], iV[3]);
double d1 = l.distance(iV[1]);
double d2 = l.distance(iV[2]);
return (d1 < precision && d2 < precision);
}
}
//! Subdivide this Bezier curve in the middle.
void Bezier::subdivide(Bezier &l, Bezier &r) const
{
Vector h;
l.iV[0] = iV[0];
l.iV[1] = 0.5 * (iV[0] + iV[1]);
h = 0.5 * (iV[1] + iV[2]);
l.iV[2] = 0.5 * (l.iV[1] + h);
r.iV[2] = 0.5 * (iV[2] + iV[3]);
r.iV[1] = 0.5 * (h + r.iV[2]);
r.iV[0] = 0.5 * (l.iV[2] + r.iV[1]);
l.iV[3] = r.iV[0];
r.iV[3] = iV[3];
}
//! Approximate by a polygonal chain.
/*! \a result must be empty when calling this. */
void Bezier::approximate(double precision, std::vector &result) const
{
if (straight(precision)) {
result.push_back(iV[3]);
} else {
Bezier l, r;
subdivide(l, r);
l.approximate(precision, result);
r.approximate(precision, result);
}
}
//! Convert a quadratic Bezier-spline to a cubic one.
/*! The quadratic Bezier-spline with control points p0, p1, p2
is identical to the cubic Bezier-spline with control points
q0 = p0, q1 = (2p1 + p0)/3, q2 = (2p1 + p2)/3, q3 = p2. */
Bezier Bezier::quadBezier(const Vector &p0, const Vector &p1,
const Vector &p2)
{
Vector q1 = thirdpoint(p1, p0);
Vector q2 = thirdpoint(p1, p2);
return Bezier(p0, q1, q2, p2);
}
//! Convert an old-style Ipe B-spline to a series of Bezier splines.
/*! For some reason lost in the mist of time, this was the definition
of splines in Ipe for many years. It doesn't use knots. The first
and last control point are simply given multiplicity 3.
Bezier splines are appended to \a result.
*/
void Bezier::oldSpline(int n, const Vector *v, std::vector &result)
{
Vector p0, p1, p2, p3, q0, q1, q2, q3;
// First segment (p1 = p2 = p0 => q1 = q2 = q0 = p0)
p0 = v[0];
p3 = v[1];
q3 = midpoint(thirdpoint(p0, p3), p0);
result.push_back(Bezier(p0, p0, p0, q3));
if (n > 2) {
// Second segment
p1 = v[0];
p2 = v[1];
p3 = v[2];
q0 = q3; // from previous
q1 = thirdpoint(p1, p2);
q2 = thirdpoint(p2, p1);
q3 = midpoint(thirdpoint(p2, p3), q2);
result.push_back(Bezier(q0, q1, q2, q3));
// create n - 3 segments
for (int i = 0; i < n - 3; ++i) {
p0 = v[i];
p1 = v[i + 1];
p2 = v[i + 2];
p3 = v[i + 3];
q0 = q3; // from previous
// q0 = midpoint(thirdpoint(p1, p0), q1); // the real formula
q1 = thirdpoint(p1, p2);
q2 = thirdpoint(p2, p1);
q3 = midpoint(thirdpoint(p2, p3), q2);
result.push_back(Bezier(q0, q1, q2, q3));
}
}
// Second to last segment
p1 = v[n-2];
p2 = v[n-1];
p3 = v[n-1];
q0 = q3; // from previous
q1 = thirdpoint(p1, p2);
q2 = thirdpoint(p2, p1);
q3 = midpoint(p3, q2);
result.push_back(Bezier(q0, q1, q2, q3));
// Last segment (p1 = p2 = p3 => q1 = q2 = q3 = p3)
result.push_back(Bezier(q3, p3, p3, p3));
}
//! Convert a clamped uniform B-spline to a series of Bezier splines.
/*! See Thomas Sederberg, Computer-Aided Geometric Design, Chapter 6.
In polar coordinates, a control point is defined by three knots, so
n control points need n + 2 knots. To clamp the spline to the first
and last control point, the first and last knot are repeated three
times. This leads to k knot intervals and the knot sequence
[0, 0, 0, 1, 2, 3, 4, 5, 6, ..., k-2, k-1, k, k, k]
There are k + 5 = n + 2 knots in this sequence, so k = n-3 is the
number of knot intervals and therefore the number of output Bezier
curves.
If n = 4, the knot sequence is [0, 0, 0, 1, 1, 1] and the result is
simply the Bezier curve on the four control points.
When n in {2, 3}, returns a single Bezier curve that is a segment or
quadratic Bezier spline. This is different from the behaviour of
the "old" Ipe splines.
Bezier splines are appended to \a result.
*/
void Bezier::spline(int n, const Vector *v, std::vector &result)
{
if (n == 2) {
result.push_back(Bezier(v[0], v[0], v[1], v[1]));
} else if (n == 3) {
result.push_back(quadBezier(v[0], v[1], v[2]));
} else if (n == 4) {
result.push_back(Bezier(v[0], v[1], v[2], v[3]));
} else if (n == 5) {
// Given are [0,0,0], [0,0,1], [0,1,2], [1,2,2], [2,2,2]
// Interval 0-1: [0,0,0], [0,0,1], [0,1,1], [1,1,1]
Vector q0 = v[0]; // [0,0,0]
Vector q1 = v[1]; // [0,0,1]
Vector q2 = midpoint(q1, v[2]); // [0,1,1] = 1/2 [0,1,0] + 1/2 [0,1,2]
Vector r = midpoint(v[2], v[3]); // [1,1,2] = 1/3 [0,1,2] + 1/2 [2,1,2]
Vector q3 = midpoint(q2, r); // [1,1,1] = 1/2 [1,0,1] + 1/2 [1,2,1]
result.push_back(Bezier(q0, q1, q2, q3));
// Interval 1-2: [1,1,1], [1,1,2], [1,2,2], [2,2,2]
result.push_back(Bezier(q3, r, v[3], v[4]));
} else {
int k = n-3;
// Interval 0-1: [0,0,0], [0,0,1], [0,1,1], [1,1,1]
Vector q0 = v[0]; // [0,0,0]
Vector q1 = v[1]; // [0,0,1]
Vector q2 = midpoint(q1, v[2]); // [0,1,1] = 1/2 [0,1,0] + 1/2 [0,1,2]
Vector r = thirdpoint(v[2], v[3]); // [1,2,1] = 2/3 [0,1,2] + 1/3 [3,1,2]
Vector q3 = midpoint(q2, r); // [1,1,1] = 1/2 [1,0,1] + 1/2 [1,2,1]
result.push_back(Bezier(q0, q1, q2, q3));
for (int i = 1; i < k-2; ++i) {
// Interval i-i+1: [i,i,i], [i,i,i+1], [i,i+1,i+1], [i+1,i+1,i+1]
q0 = q3; // [i,i,i]
q1 = r; // [i,i,i+1]
// [i,i+1,i+1] = 1/2 [i,i+1,i] + 1/2 [i, i+1, i+2]
q2 = midpoint(q1, v[i+2]);
// [i+1,i+1,i+2] = 2/3 [i,i+1,i+2] + [i+3,i+1,i+2]
r = thirdpoint(v[i+2], v[i+3]);
// [i+1,i+1,i+1] = 1/2 [i+1,i+1,i] + 1/2 [i+1,i+1,i+2]
q3 = midpoint(q2, r);
result.push_back(Bezier(q0, q1, q2, q3));
}
// Interval (k-2)-(k-1):
// [k-2,k-2,k-2], [k-2,k-2,k-1], [k-2,k-1,k-1], [k-1,k-1,k-1]
q0 = q3;
q1 = r; // [k-2,k-2,k-1]
// [k-2,k-1,k-1] = 1/2 [k-2,k-1,k-2] + 1/2 [k-2,k-1,k]
q2 = midpoint(q1, v[k]);
// [k-1,k-1,k] = 1/2 [k-2,k-1,k] + 1/2 [k,k-1,k]
r = midpoint(v[k], v[k+1]);
// [k-1,k-1,k-1] = 1/2 [k-1,k-1,k-2] + 1/2 [k-1,k-1,k]
q3 = midpoint(q2, r);
result.push_back(Bezier(q0, q1, q2, q3));
// Interval (k-1)-k: [k-1,k-1,k-1], [k-1,k-1,k], [k-1,k,k], [k,k,k]
q0 = q3;
q1 = r;
q2 = v[n-2]; // [k-1,k,k]
q3 = v[n-1]; // [k,k,k]
result.push_back(Bezier(q0, q1, q2, q3));
}
}
//! Convert a closed uniform cubic B-spline to a series of Bezier splines.
/*! Bezier splines are appended to \a result. */
void Bezier::closedSpline(int n, const Vector *v, std::vector &result)
{
for (int i = 0; i < n; ++i) {
Vector p0 = v[i % n]; // [0, 1, 2]
Vector p1 = v[(i+1) % n]; // [1, 2, 3]
Vector p2 = v[(i+2) % n]; // [2, 3, 4]
Vector p3 = v[(i+3) % n]; // [3, 4, 5]
Vector r = thirdpoint(p1, p0); // [1, 2, 2]
Vector u = thirdpoint(p2, p3); // [3, 3, 4]
Vector q1 = thirdpoint(p1, p2); // [2, 2, 3]
Vector q2 = thirdpoint(p2, p1); // [2, 3, 3]
Vector q0 = midpoint(r, q1); // [2, 2, 2]
Vector q3 = midpoint(u, q2); // [3, 3, 3]
result.push_back(Bezier(q0, q1, q2, q3));
}
}
//! Return distance to Bezier spline.
/*! But may just return \a bound if actual distance is larger. The
Bezier spline is approximated to a precision of 1.0, and the
distance to the approximation is returned.
*/
double Bezier::distance(const Vector &v, double bound)
{
Rect box;
box.addPoint(iV[0]);
box.addPoint(iV[1]);
box.addPoint(iV[2]);
box.addPoint(iV[3]);
if (box.certainClearance(v, bound))
return bound;
std::vector approx;
approximate(1.0, approx);
Vector cur = iV[0];
double d = bound;
double d1;
for (std::vector::const_iterator it = approx.begin();
it != approx.end(); ++it) {
if ((d1 = Segment(cur, *it).distance(v, d)) < d)
d = d1;
cur = *it;
}
return d;
}
//! Return a tight bounding box (accurate to within 0.5).
Rect Bezier::bbox() const
{
Rect box(iV[0]);
std::vector approx;
approximate(0.5, approx);
for (std::vector::const_iterator it = approx.begin();
it != approx.end(); ++it) {
box.addPoint(*it);
}
return Rect(box.bottomLeft() - Vector(0.5, 0.5),
box.topRight() + Vector(0.5, 0.5));
}
//! Find (approximately) nearest point on Bezier spline.
/*! Find point on spline nearest to \a v, but only if
it is closer than \a bound.
If a point is found, sets \a t to the parameter value and
\a pos to the actual point, and returns true. */
bool Bezier::snap(const Vector &v, double &t, Vector &pos, double &bound) const
{
Rect box(iV[0], iV[1]);
box.addPoint(iV[2]);
box.addPoint(iV[3]);
if (box.certainClearance(v, bound))
return false;
// handle straight ends of B-splines
if (iV[0] != iV[1] && iV[1] == iV[2] && iV[2] == iV[3]) {
Vector prj;
double d;
if (Segment(iV[0], iV[3]).project(v, prj) &&
(d = (v - prj).len()) < bound) {
bound = d;
pos = prj;
t = 1.0 - pow((pos - iV[3]).len()/(iV[0] - iV[3]).len(), 1.0/3.0);
return true;
} // endpoints handled by code below
}
if (iV[0] == iV[1] && iV[1] == iV[2] && iV[2] != iV[3]) {
Vector prj;
double d;
if (Segment(iV[3], iV[0]).project(v, prj) &&
(d = (v - prj).len()) < bound) {
bound = d;
pos = prj;
t = 1.0 - pow((pos - iV[0]).len()/(iV[3] - iV[0]).len(), 1.0/3.0);
return true;
} // endpoints handled by code below
}
if (straight(1.0)) {
Vector prj;
if (iV[0] != iV[3] && Segment(iV[0], iV[3]).project(v, prj)) {
double t1 = (prj - iV[0]).len() / (iV[3] - iV[0]).len();
Vector u = point(t1);
double d = (v - u).len();
if (d < bound) {
t = t1;
bound = d;
pos = u;
return true;
} else
return false;
} else {
bool v0 = iV[0].snap(v, pos, bound);
bool v1 = iV[3].snap(v, pos, bound);
if (v0)
t = 0.0;
if (v1)
t = 1.0;
return v0 || v1;
}
} else {
Bezier l, r;
subdivide(l, r);
bool p1 = l.snap(v, t, pos, bound);
bool p2 = r.snap(v, t, pos, bound);
if (p1 || p2)
t = 0.5 * t;
if (p2)
t = t + 0.5;
return p1 || p2;
}
}
// --------------------------------------------------------------------
/* Determines intersection point(s) of two cubic Bezier-Splines.
* The found intersection points are stored in the vector intersections.
*/
static void intersectBeziers(std::vector &intersections,
const Bezier &a, const Bezier &b)
{
/* Recursive approximation procedure to find intersections:
* If the bounding boxes of two Beziers overlap, both are subdivided,
* each one into two partial Beziers.
* In the next recursion steps, it is checked if the bounding boxes
* of the partial Beziers overlap. If they do, they are subdivided
* again and so on, until a special precision is achieved:
* Then the Beziers are converted to Segments and checked for intersection.
*/
Rect abox(a.iV[0], a.iV[1]);
abox.addPoint(a.iV[2]);
abox.addPoint(a.iV[3]);
Rect bbox(b.iV[0], b.iV[1]);
bbox.addPoint(b.iV[2]);
bbox.addPoint(b.iV[3]);
if (!abox.intersects(bbox))
return;
if (a.straight(BEZIER_INTERSECT_PRECISION) &&
b.straight(BEZIER_INTERSECT_PRECISION)) {
Segment as = Segment(a.iV[0], a.iV[3]);
Segment bs = Segment(b.iV[0], b.iV[3]);
Vector p;
if (as.intersects(bs, p))
intersections.push_back(p);
} else {
Bezier leftA, rightA, leftB, rightB;
a.subdivide(leftA, rightA);
b.subdivide(leftB, rightB);
intersectBeziers(intersections, leftA, leftB);
intersectBeziers(intersections, rightA, leftB);
intersectBeziers(intersections, leftA, rightB);
intersectBeziers(intersections, rightA, rightB);
}
}
//! Compute intersection points of Bezier with Line.
void Bezier::intersect(const Line &l, std::vector &result) const
{
double sgn = l.side(iV[0]);
if (sgn < 0 && l.side(iV[1]) < 0 && l.side(iV[2]) < 0 && l.side(iV[3]) < 0)
return;
if (sgn > 0 && l.side(iV[1]) > 0 && l.side(iV[2]) > 0 && l.side(iV[3]) > 0)
return;
if (straight(BEZIER_INTERSECT_PRECISION)) {
Vector p;
if (Segment(iV[0], iV[3]).intersects(l, p))
result.push_back(p);
} else {
Bezier leftA, rightA;
subdivide(leftA, rightA);
leftA.intersect(l, result);
rightA.intersect(l, result);
}
}
//! Compute intersection points of Bezier with Segment.
void Bezier::intersect(const Segment &s, std::vector &result) const
{
// convert Segment to Bezier and use Bezier-Bezier-intersection
// this works well since the segment is immediately "straight"
intersectBeziers(result, *this, Bezier(s.iQ, s.iQ, s.iP, s.iP));
}
//! Compute intersection points of Bezier with Bezier.
void Bezier::intersect(const Bezier &b, std::vector &result) const
{
intersectBeziers(result, *this, b);
}
// --------------------------------------------------------------------
/*! \class ipe::Arc
\ingroup geo
\brief An arc of an ellipse.
The ellipse is represented using the matrix that transforms the unit
circle x^2 + y^2 = 1 to the desired ellipse. The arc coordinate
system is the coordinate system of this unit circle.
A full ellipse is described by iAlpha = 0, iBeta = IpeTwoPi.
An elliptic arc is the image of the circular arc from iAlpha to
iBeta (in increasing angle in arc coordinate system).
*/
//! Construct arc for ellipse defined by m, from begp to endp.
/*! This assumes that \a m has been correctly computed such that \a
begb and \a endp already lie on the ellipse. */
Arc::Arc(const Matrix &m, const Vector &begp, const Vector &endp)
{
iM = m;
Matrix inv = m.inverse();
iAlpha = (inv * begp).angle();
iBeta = (inv * endp).angle();
}
//! This doesn't really compute the distance, but a reasonable approximation.
double Arc::distance(const Vector &v, double bound) const
{
Vector pos;
Angle angle;
return distance(v, bound, pos, angle);
}
/*! Like distance(), but sets pos to point on arc and angle to its angle
in arc coordinates.
\a angle and \a pos are not modified if result is larger than bound.
*/
double Arc::distance(const Vector &v, double bound,
Vector &pos, Angle &angle) const
{
Matrix inv1 = iM.inverse();
Vector v1 = inv1 * v;
Vector pos1 = iM * v1.normalized();
double d = (v - pos1).len();
if (isEllipse()) {
if (d < bound) {
bound = d;
pos = pos1;
angle = v1.angle();
}
} else {
// elliptic arc
if (d < bound && v1.angle().liesBetween(iAlpha, iBeta)) {
bound = d;
pos = pos1;
angle = v1.angle();
}
pos1 = iM * Vector(iAlpha);
d = (v - pos1).len();
if (d < bound) {
bound = d;
pos = pos1;
angle = iAlpha;
}
pos1 = iM * Vector(iBeta);
d = (v - pos1).len();
if (d < bound) {
bound = d;
pos = pos1;
angle = iBeta;
}
}
return bound;
}
//! Return a tight bounding box.
Rect Arc::bbox() const
{
Rect box;
// add begin and end point
box.addPoint(iM * Vector(iAlpha));
box.addPoint(iM * Vector(iBeta));
Linear inv = iM.linear().inverse();
bool ell = isEllipse();
Angle alpha = (inv * Vector(0,1)).angle() - IpeHalfPi;
if (ell || alpha.liesBetween(iAlpha, iBeta))
box.addPoint(iM * Vector(alpha));
alpha = (inv * Vector(0,-1)).angle() - IpeHalfPi;
if (ell || alpha.liesBetween(iAlpha, iBeta))
box.addPoint(iM * Vector(alpha));
alpha = (inv * Vector(1,0)).angle() - IpeHalfPi;
if (ell || alpha.liesBetween(iAlpha, iBeta))
box.addPoint(iM * Vector(alpha));
alpha = (inv * Vector(-1,0)).angle() - IpeHalfPi;
if (ell || alpha.liesBetween(iAlpha, iBeta))
box.addPoint(iM * Vector(alpha));
return box;
}
// --------------------------------------------------------------------
//! Compute intersection points of Arc with Line.
void Arc::intersect(const Line &l, std::vector &result) const
{
Matrix m = iM.inverse();
Vector p = m * l.iP;
Vector d = (m.linear() * l.dir()).normalized();
// solve quadratic equation
double b = 2 * dot(p, d);
double c = dot(p, p) - 1.0;
double D = b*b - 4*c;
if (D < 0)
return;
double sD = (b < 0) ? -sqrt(D) : sqrt(D);
double t1 = -0.5 * (b + sD);
Vector v = p + t1 * d;
if (v.angle().liesBetween(iAlpha, iBeta))
result.push_back(iM * v);
if (D > 0) {
v = p + (c/t1) * d;
if (v.angle().liesBetween(iAlpha, iBeta))
result.push_back(iM * v);
}
}
//! Compute intersection points of Arc with Segment.
void Arc::intersect(const Segment &s, std::vector &result) const
{
std::vector pt;
intersect(s.line(), pt);
Vector dir = s.iQ - s.iP;
for (int i = 0; i < size(pt); ++i) {
// check whether it's on the segment
Vector v = pt[i];
if (dot(v - s.iP, dir) >= 0 && dot(v - s.iQ, dir) <= 0)
result.push_back(v);
}
}
//! Compute intersection points of Arc with Arc.
void Arc::intersect(const Arc &a, std::vector &result) const
{
/* Recursive approximation procedure to find intersections:
* If the bounding boxes of two Arcs overlap, both are subdivided,
* each one into two partial Arcs.
* In the next recursion steps, it is checked if the bounding boxes
* of the partial Arcs overlap. If they do, they are subdivided
* again and so on, until a special precision is achieved.
*/
const double PRECISION = 0.05; // 0.05 is about ~2.8647 degrees
if (!bbox().intersects(a.bbox()))
return;
if (straight(PRECISION) && a.straight(PRECISION)) {
intersect(Segment(a.beginp(), a.endp()), result);
} else {
Arc al, ar;
subdivide(al, ar);
Arc bl, br;
a.subdivide(bl, br);
al.intersect(bl, result);
al.intersect(br, result);
ar.intersect(bl, result);
ar.intersect(br, result);
}
}
//! Compute intersection points of Arc with Bezier.
void Arc::intersect(const Bezier &b, std::vector &result) const
{
/* Recursive approximation procedure to find intersections: If the
* bounding boxes of the Bezier and the Arc overlap, both are
* subdivided, the Bezier into two Beziers and the Arc into two
* Arcs. In the next recursion steps, it is checked if a bounding
* box of a partial Bezier overlap one of a partial Arc. If they
* overlap, they are subdivided again and so on, until a special
* precision is achieved.
*/
const double PRECISION = 0.05; // 0.05 is about ~2.8647 degrees
Rect bboxB(b.iV[0], b.iV[1]);
bboxB.addPoint(b.iV[2]);
bboxB.addPoint(b.iV[3]);
if (!bbox().intersects(bboxB))
return;
if (b.straight(PRECISION)) {
intersect(Segment(b.iV[0], b.iV[3]), result);
} else {
// is it really useful to divide the arc?
// the hope is to achieve emptiness of intersection more quickly
Arc al, ar;
subdivide(al, ar);
Bezier bl, br;
b.subdivide(bl, br);
al.intersect(bl, result);
al.intersect(br, result);
ar.intersect(bl, result);
ar.intersect(br, result);
}
}
//! Subdivide this arc into two.
void Arc::subdivide(Arc &l, Arc &r) const
{
if (iAlpha == 0.0 && iBeta == IpeTwoPi) {
l = Arc(iM, Angle(0), Angle(IpePi));
r = Arc(iM, Angle(IpePi), Angle(IpeTwoPi));
} else {
// delta is length of arc
double delta = Angle(iBeta).normalize(iAlpha) - iAlpha;
Angle gamma(iAlpha + delta/2);
l = Arc(iM, iAlpha, gamma);
r = Arc(iM, gamma, iBeta);
}
}
/*! Returns true if the difference between start- and endangle is less
* than precision.
*/
bool Arc::straight(const double precision) const
{
if (iAlpha == 0.0 && iBeta == IpeTwoPi)
return false;
return Angle(iBeta).normalize(iAlpha) - iAlpha < precision;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipebitmap_win.cpp 0000644 0001750 0001750 00000007707 13561570220 017635 0 ustar otfried otfried // ipebitmap_win.cpp
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipebitmap.h"
#include "ipeutils.h"
#include
#include
#include
using namespace ipe;
// --------------------------------------------------------------------
typedef IStream* WINAPI (*LPFNSHCREATEMEMSTREAM)(const BYTE *, UINT);
static bool libLoaded = false;
static LPFNSHCREATEMEMSTREAM pSHCreateMemStream = nullptr;
bool dctDecode(Buffer dctData, Buffer pixelData)
{
if (!libLoaded) {
libLoaded = true;
HMODULE hDll = LoadLibraryA("shlwapi.dll");
if (hDll)
pSHCreateMemStream =
(LPFNSHCREATEMEMSTREAM) GetProcAddress(hDll, (LPCSTR) 12);
}
if (!pSHCreateMemStream)
return false;
IStream *stream = pSHCreateMemStream((const BYTE *) dctData.data(),
dctData.size());
if (!stream)
return false;
Gdiplus::Bitmap *bitmap = Gdiplus::Bitmap::FromStream(stream);
if (bitmap->GetLastStatus() != Gdiplus::Ok) {
delete bitmap;
stream->Release();
return false;
}
int w = bitmap->GetWidth();
int h = bitmap->GetHeight();
// ipeDebug("dctDecode: %d x %d format %x", w, h, bitmap->GetPixelFormat());
Gdiplus::BitmapData* bitmapData = new Gdiplus::BitmapData;
bitmapData->Scan0 = pixelData.data();
bitmapData->Stride = 4 * w;
bitmapData->Width = w;
bitmapData->Height = h;
bitmapData->PixelFormat = PixelFormat32bppARGB;
Gdiplus::Rect rect(0, 0, w, h);
bitmap->LockBits(&rect,
Gdiplus::ImageLockModeRead |
Gdiplus::ImageLockModeUserInputBuf,
PixelFormat32bppARGB, bitmapData);
bitmap->UnlockBits(bitmapData);
delete bitmapData;
delete bitmap;
stream->Release();
return true;
}
// --------------------------------------------------------------------
// The graphics file formats supported by GDI+ are
// BMP, GIF, JPEG, PNG, TIFF.
Bitmap Bitmap::readPNG(const char *fname, Vector &dotsPerInch, const char * &errmsg)
{
// load without color correction
Gdiplus::Bitmap *bitmap = Gdiplus::Bitmap::FromFile(String(fname).w().data(), FALSE);
if (bitmap->GetLastStatus() != Gdiplus::Ok) {
delete bitmap;
return Bitmap();
}
dotsPerInch = Vector(bitmap->GetHorizontalResolution(),
bitmap->GetVerticalResolution());
int w = bitmap->GetWidth();
int h = bitmap->GetHeight();
Buffer pixelData(4 * w * h);
Gdiplus::BitmapData* bitmapData = new Gdiplus::BitmapData;
bitmapData->Scan0 = pixelData.data();
bitmapData->Stride = 4 * w;
bitmapData->Width = w;
bitmapData->Height = h;
bitmapData->PixelFormat = PixelFormat32bppARGB;
Gdiplus::Rect rect(0, 0, w, h);
bitmap->LockBits(&rect,
Gdiplus::ImageLockModeRead |
Gdiplus::ImageLockModeUserInputBuf,
PixelFormat32bppARGB, bitmapData);
Bitmap bm(w, h, Bitmap::ENative, pixelData);
bitmap->UnlockBits(bitmapData);
delete bitmapData;
delete bitmap;
return bm;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipeobject.cpp 0000644 0001750 0001750 00000032621 13561570220 016743 0 ustar otfried otfried // --------------------------------------------------------------------
// The Ipe object type
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*! \mainpage The Ipe library documentation
The Ipe library ("Ipelib") provides the geometric primitives and
implements all the geometric objects that appear in Ipe. Many
tasks related to modifying an Ipe document are actually performed
by Ipelib. For instance, the ipetoipe program consists of only a
few calls to Ipelib.
Ipelib can easily be used by C++ programs to read, write, and
modify Ipe documents. Compiling Ipelib is easy, it requires only
the standard C++ library (including the STL), and the zlib
compression library. Nearly all symbols in Ipelib are in the \ref
ipe "ipe" namespace, those that aren't start with the letters
"Ipe".
Before using Ipelib in your own program, make sure to initialize
the library by calling ipe::Platform::initLib().
Many of the Ipelib classes are also made available as Lua objects.
\subpage lua describes the Lua bindings to Ipelib, to ipeui, and to
the Ipe program itself. The Ipe program itself is mostly written
in Lua and uses these Lua bindings.
On Unix, all filenames passed to Ipelib are assumed to be in the
local system's encoding. On Windows, all filenames passed to
Ipelib are assumed to be UTF-8. All Lua strings are assumed to be
UTF-8 (filenames are converted by the ipelua bindings).
\subpage ipelets explains how to write ipelets, that is, extensions
to Ipe. Ipelets are either written in Lua or in C++ (using a small
Lua wrapper to describe the ipelet). C++ ipelets have to be linked
with Ipelib to access and modify Ipe objects.
The classes documented here are implemented in five different libraries:
\li \e libipe is the core Ipelib library. It implements geometric
primitives, Ipe objects, and the Ipe document. However, it doesn't
know anything about drawing to the screen or about the Lua
bindings.
\li \e libipecairo implements the \ref cairo "Ipe cairo module".
It provides drawing of Ipe objects using the Cairo library.
\li \e libipelua implements the \ref lua "Lua bindings" for Ipelib.
If installed properly, it can be loaded dynamically from Lua using
\c require. It is also used by ipescript.
\li \e libipecanvas implements the \ref qtcanvas "Ipe canvas module".
It provides a widget for displaying and editing Ipe objects.
\li \e libipeui implements Lua bindings for user interfaces. This
library does not depend on any other Ipe component, and can be used
for other Lua projects.
Here is an annotated list of the modules:
\li \ref base : Some basic datatypes: ipe::String, ipe::Buffer,
ipe::Stream, ipe::Fixed
\li \ref geo : Geometric types and linear algebra: ipe::Vector,
ipe::Matrix, ipe::Line, ipe::Segment, ipe::Arc, ipe::Bezier, ipe::Shape.
\li \ref attr : Attributes such as ipe::Color, ipe::Kind, ipe::Attribute
\li \ref obj : The five ipe::Object types: ipe::Group, ipe::Path,
ipe::Text, ipe::Image, and ipe::Reference
\li \ref doc : The Ipe document: ipe::Document, ipe::Page, and
ipe::StyleSheet
\li \ref high : Some utility classes: ipe::ImlParser, ipe::BitmapFinder, etc.
\li \ref ipelet : The ipe::Ipelet interface
\li \ref cairo : Classes to draw Ipe objects on a Cairo surface:
ipe::CairoPainter, ipe::Fonts, ipe::Face
\li \ref canvas : A widget ipe::Canvas to display Ipe objects,
and tools for this canvas: ipe::PanTool, ipe::SelectTool,
ipe::TransformTool.
Finally, here is list of the pages describing Lua bindings:
\li \ref luageo Lua bindings for basic geometric objects
\li \ref luaobj Lua bindings for Ipe objects
\li \ref luapage Lua bindings for documents, pages, and stylesheets
\li \ref luaipeui Lua bindings for dialogs, menus, etc.
\li \ref luaipe Lua bindings for the Ipe program itself.
*/
/* namespace ipe
brief Ipe library namespace
Nearly all symbols defined by the Ipe library and the Ipe-Cairo
interface are in the namespace ipe. (Other symbols all start with
the letters "Ipe" (or "ipe" or "IPE").
*/
// --------------------------------------------------------------------
/*! \defgroup obj Ipe Objects
\brief The Ipe object model.
This module deals with the actual objects inside an Ipe document.
All Ipe objects are derived from Object.
*/
#include "ipegeo.h"
#include "ipeobject.h"
#include "ipepainter.h"
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::Object
\ingroup obj
\brief Base class for all Ipe objects, composite or leaf.
All objects are derived from this class. It provides functionality
common to all objects, and carries the standard attributes.
All Object's provide a constant time copy constructor (and a virtual
Object::clone() method). Objects of non-constant size realize this
by separating the implementation and using reference counting. In
particular, copying a composite object does not create new copies of
the components.
Object has only three attributes: the transformation matrix, the
pinning status, and the allowed transformations.
If an object is pinned, it cannot be moved at all (or only in the
non-pinned direction) from the Ipe user interface.
Restricting the allowed transformations works somewhat differently:
It doesn't stop transformations being applied to the object, but
they only effect the position of the reference point (the origin of
the object coordinate system), and (if transformations() ==
ETransformationsRigidMotions) the orientation of the object
coordinate system.
*/
//! Construct from XML stream.
Object::Object(const XmlAttributes &attr)
{
String str;
if (attr.has("matrix", str))
iMatrix = Matrix(str);
iPinned = ENoPin;
if (attr.has("pin", str)) {
if (str == "yes")
iPinned = EFixedPin;
else if (str == "h")
iPinned = EHorizontalPin;
else if (str == "v")
iPinned = EVerticalPin;
}
iTransformations = ETransformationsAffine;
if (attr.has("transformations", str) && !str.empty()) {
if (str == "rigid")
iTransformations = ETransformationsRigidMotions;
else if (str == "translations")
iTransformations = ETransformationsTranslations;
}
}
/*! Create object by taking pinning/transforming from \a attr and
setting identity matrix. */
Object::Object(const AllAttributes &attr)
{
iPinned = attr.iPinned;
iTransformations = attr.iTransformations;
}
/*! Create object with identity matrix, no pinning, all transformations. */
Object::Object()
{
iPinned = ENoPin;
iTransformations = ETransformationsAffine;
}
//! Copy constructor.
Object::Object(const Object &rhs)
{
iMatrix = rhs.iMatrix;
iPinned = rhs.iPinned;
iTransformations = rhs.iTransformations;
}
//! Pure virtual destructor.
Object::~Object()
{
// nothing
}
//! Write layer, pin, transformations, matrix to XML stream.
void Object::saveAttributesAsXml(Stream &stream, String layer) const
{
if (!layer.empty())
stream << " layer=\"" << layer << "\"";
if (!iMatrix.isIdentity())
stream << " matrix=\"" << iMatrix << "\"";
switch (iPinned) {
case EFixedPin:
stream << " pin=\"yes\"";
break;
case EHorizontalPin:
stream << " pin=\"h\"";
break;
case EVerticalPin:
stream << " pin=\"v\"";
break;
case ENoPin:
default:
break;
}
if (iTransformations == ETransformationsTranslations)
stream << " transformations=\"translations\"";
else if (iTransformations == ETransformationsRigidMotions)
stream << " transformations=\"rigid\"";
}
//! Return pointer to this object if it is an Group, nullptr otherwise.
Group *Object::asGroup()
{
return nullptr;
}
//! Return pointer to this object if it is an Group, nullptr otherwise.
const Group *Object::asGroup() const
{
return nullptr;
}
//! Return pointer to this object if it is an Text, nullptr otherwise.
Text *Object::asText()
{
return nullptr;
}
//! Return pointer to this object if it is an Path, nullptr otherwise.
Path *Object::asPath()
{
return nullptr;
}
//! Return pointer to this object if it is an Image , nullptr otherwise.
Image *Object::asImage()
{
return nullptr;
}
//! Return pointer to this object if it is an Ref, nullptr otherwise.
Reference *Object::asReference()
{
return nullptr;
}
// --------------------------------------------------------------------
//! Set the transformation matrix.
/*! Don't use this on an Object in a Page, because it wouldn't
invalidate its bounding box. Call Page::transform instead. */
void Object::setMatrix(const Matrix &matrix)
{
iMatrix = matrix;
}
//! Return pinning mode of the object.
TPinned Object::pinned() const
{
return iPinned;
}
//! Set pinning mode of the object.
void Object::setPinned(TPinned pin)
{
iPinned = pin;
}
//! Set allowed transformations of the object.
void Object::setTransformations(TTransformations trans)
{
iTransformations = trans;
}
//! Set an attribute on this object.
/*! Returns true if an attribute was actually changed. */
bool Object::setAttribute(Property prop, Attribute value)
{
switch (prop) {
case EPropPinned:
assert(value.isEnum());
if (value.pinned() != iPinned) {
iPinned = value.pinned();
return true;
}
break;
case EPropTransformations:
assert(value.isEnum());
if (value.transformations() != iTransformations) {
iTransformations = value.transformations();
return true;
}
break;
default:
break;
}
return false;
}
//! Get setting of an attribute of this object.
/*! If object does not have this attribute, returnes "undefined"
attribute. */
Attribute Object::getAttribute(Property prop) const noexcept
{
switch (prop) {
case EPropPinned:
return Attribute(pinned());
case EPropTransformations:
return Attribute(iTransformations);
default:
return Attribute::UNDEFINED();
}
}
// --------------------------------------------------------------------
//! Check all symbolic attributes.
void Object::checkStyle(const Cascade *, AttributeSeq &) const
{
// nothing
}
/*! Check whether attribute \a is either absolute or defined in the
style sheet cascade \a sheet. Add \a attr to \a seq if this is not
the case. */
void Object::checkSymbol(Kind kind, Attribute attr, const Cascade *sheet,
AttributeSeq &seq)
{
if (attr.isSymbolic() && sheet->findDefinition(kind, attr) < 0) {
AttributeSeq::const_iterator it =
std::find(seq.begin(), seq.end(), attr);
if (it == seq.end())
seq.push_back(attr);
}
}
//! Compute vertex snapping position for transformed object.
/*! Looks only for positions closer than \a bound.
If successful, modify \a pos and \a bound.
The default implementation does nothing.
*/
void Object::snapVtx(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
// nothing
}
//! Compute control point snapping position for transformed object.
/*! Looks only for positions closer than \a bound.
If successful, modify \a pos and \a bound.
The default implementation does nothing.
*/
void Object::snapCtl(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
// nothing
}
//! Compute boundary snapping position for transformed object.
/*! Looks only for positions closer than \a bound.
If successful, modify \a pos and \a bound.
The default implementation does nothing.
*/
void Object::snapBnd(const Vector &/* mouse */, const Matrix &/* m */,
Vector &/* pos */, double &/* bound */) const
{
// nothing
}
// --------------------------------------------------------------------
/*! \class ipe::Visitor
\ingroup high
\brief Base class for visitors to Object.
Many operations on Ipe Objects are implemented as visitors, all
derived from Visitor.
The default implementation of each visitXXX member does nothing.
*/
//! Pure virtual destructor.
Visitor::~Visitor()
{
// void
}
//! Called on an Group object.
void Visitor::visitGroup(const Group *)
{
// nothing
}
//! Called on an Path object.
void Visitor::visitPath(const Path *)
{
// nothing
}
//! Called on an Image object.
void Visitor::visitImage(const Image * )
{
// nothing
}
//! Called on an Text object.
void Visitor::visitText(const Text * )
{
// nothing
}
//! Called on an Reference object.
void Visitor::visitReference(const Reference * )
{
// nothing
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipestyle.cpp 0000644 0001750 0001750 00000054301 13561570220 016634 0 ustar otfried otfried // --------------------------------------------------------------------
// Ipe style sheet
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipestyle.h"
#include "ipeobject.h"
#include "ipepainter.h"
#include "ipeutils.h"
#include "ipeiml.h"
#include
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::Symbol
\ingroup attr
\brief A symbol is a named object defined in an ipe::StyleSheet.
*/
//! Default constructor
Symbol::Symbol()
{
iObject = nullptr;
iXForm = false;
iTransformations = ETransformationsAffine;
}
//! Create symbol for \a object (takes ownership).
Symbol::Symbol(Object *object)
{
iObject = object;
iXForm = false;
iTransformations = ETransformationsAffine;
}
//! Copy constructor.
Symbol::Symbol(const Symbol &rhs)
{
iObject = rhs.iObject ? rhs.iObject->clone() : nullptr;
iXForm = rhs.iXForm;
iTransformations = rhs.iTransformations;
iSnap = rhs.iSnap;
}
//! Assignment operator.
Symbol &Symbol::operator=(const Symbol &rhs)
{
if (this != &rhs) {
delete iObject;
iObject = rhs.iObject ? rhs.iObject->clone() : nullptr;
iXForm = rhs.iXForm;
iTransformations = rhs.iTransformations;
iSnap = rhs.iSnap;
}
return *this;
}
//! Destructor.
Symbol::~Symbol()
{
delete iObject;
}
// --------------------------------------------------------------------
/*! \class ipe::StyleSheet
\ingroup doc
\brief A style sheet maps symbolic names to absolute values.
Ipe documents can use symbolic attributes, such as 'normal', 'fat',
or 'thin' for line thickness, or 'red', 'navy', 'turquoise' for
color. The mapping to an absolute pen thickness or RGB value is
performed by a StyleSheet.
Style sheets are always included when the document is saved, so that
an Ipe document is self-contained.
The built-in standard style sheet is minimal, and only needed to
provide sane fallbacks for all the "normal" settings.
*/
#define MASK 0x00ffffff
#define SHIFT 24
#define KINDMASK 0x7f000000
// --------------------------------------------------------------------
//! The default constructor creates an empty style sheet.
StyleSheet::StyleSheet()
{
iStandard = false;
iTitleStyle.iDefined = false;
iPageNumberStyle.iDefined = false;
iTextPadding.iLeft = -1.0;
iLineJoin = EDefaultJoin;
iLineCap = EDefaultCap;
iFillRule = EDefaultRule;
}
//! Set page layout.
void StyleSheet::setLayout(const Layout &layout)
{
iLayout = layout;
}
//! Return page layout (or 0 if none defined).
const Layout *StyleSheet::layout() const
{
if (iLayout.isNull())
return nullptr;
else
return &iLayout;
}
//! Return text object padding (for bbox computation).
const TextPadding *StyleSheet::textPadding() const
{
if (iTextPadding.iLeft < 0)
return nullptr;
else
return &iTextPadding;
}
//! Set padding for text object bbox computation.
void StyleSheet::setTextPadding(const TextPadding &pad)
{
iTextPadding = pad;
}
//! Set style of page titles.
void StyleSheet::setTitleStyle(const TitleStyle &ts)
{
iTitleStyle = ts;
}
//! Return title style (or 0 if none defined).
const StyleSheet::TitleStyle *StyleSheet::titleStyle() const
{
if (iTitleStyle.iDefined)
return &iTitleStyle;
else
return nullptr;
}
//! Set style of page numbering.
void StyleSheet::setPageNumberStyle(const PageNumberStyle &pns)
{
iPageNumberStyle = pns;
}
//! Return page number style.
const StyleSheet::PageNumberStyle *StyleSheet::pageNumberStyle() const
{
if (iPageNumberStyle.iDefined)
return &iPageNumberStyle;
else
return nullptr;
}
//! Add gradient to this style sheet.
void StyleSheet::addGradient(Attribute name, const Gradient &s)
{
assert(name.isSymbolic());
iGradients[name.index()] = s;
}
//! Find gradient in style sheet cascade.
const Gradient *StyleSheet::findGradient(Attribute sym) const
{
if (!sym.isSymbolic())
return nullptr;
GradientMap::const_iterator it = iGradients.find(sym.index());
if (it != iGradients.end())
return &it->second;
else
return nullptr;
}
//! Add tiling to this style sheet.
void StyleSheet::addTiling(Attribute name, const Tiling &s)
{
assert(name.isSymbolic());
iTilings[name.index()] = s;
}
//! Find tiling in style sheet cascade.
const Tiling *StyleSheet::findTiling(Attribute sym) const
{
if (!sym.isSymbolic())
return nullptr;
TilingMap::const_iterator it = iTilings.find(sym.index());
if (it != iTilings.end())
return &it->second;
else
return nullptr;
}
void StyleSheet::addEffect(Attribute name, const Effect &e)
{
assert(name.isSymbolic());
iEffects[name.index()] = e;
}
const Effect *StyleSheet::findEffect(Attribute sym) const
{
if (!sym.isSymbolic())
return nullptr;
EffectMap::const_iterator it = iEffects.find(sym.index());
if (it != iEffects.end())
return &it->second;
else
return nullptr;
}
// --------------------------------------------------------------------
//! Set line cap.
void StyleSheet::setLineCap(TLineCap s)
{
iLineCap = s;
}
//! Set line join.
void StyleSheet::setLineJoin(TLineJoin s)
{
iLineJoin = s;
}
//! Set fill rule.
void StyleSheet::setFillRule(TFillRule s)
{
iFillRule = s;
}
// --------------------------------------------------------------------
//! Add a symbol object.
void StyleSheet::addSymbol(Attribute name, const Symbol &symbol)
{
assert(name.isSymbolic());
iSymbols[name.index()] = symbol;
}
//! Find a symbol object with given name.
/*! If attr is not symbolic or if the symbol doesn't exist, returns 0. */
const Symbol *StyleSheet::findSymbol(Attribute attr) const
{
if (!attr.isSymbolic())
return nullptr;
SymbolMap::const_iterator it = iSymbols.find(attr.index());
if (it != iSymbols.end())
return &it->second;
else
return nullptr;
}
// --------------------------------------------------------------------
//! Add an attribute.
/*! Does nothing if \a name is not symbolic. */
void StyleSheet::add(Kind kind, Attribute name, Attribute value)
{
if (!name.isSymbolic())
return;
iMap[name.index() | (kind << SHIFT)] = value;
}
//! Find a symbolic attribute.
/*! If \a sym is not symbolic, returns \a sym itself. If \a sym
cannot be found, returns the "undefined" attribute. In all other
cases, the returned attribute is guaranteed to be absolute.
*/
Attribute StyleSheet::find(Kind kind, Attribute sym) const
{
if (!sym.isSymbolic())
return sym;
Map::const_iterator it = iMap.find(sym.index() | (kind << SHIFT));
if (it != iMap.end()) {
return it->second;
} else
return Attribute::UNDEFINED();
}
//! Check whether symbolic attribute is defined.
/*! This method also works for ESymbol, EGradient, ETiling, and
EEffect.
Returns true if \a sym is not symbolic. */
bool StyleSheet::has(Kind kind, Attribute sym) const
{
if (!sym.isSymbolic())
return true;
switch (kind) {
case ESymbol: {
SymbolMap::const_iterator it = iSymbols.find(sym.index());
return (it != iSymbols.end()); }
case EGradient: {
GradientMap::const_iterator it = iGradients.find(sym.index());
return (it != iGradients.end()); }
case ETiling: {
TilingMap::const_iterator it = iTilings.find(sym.index());
return (it != iTilings.end()); }
case EEffect: {
EffectMap::const_iterator it = iEffects.find(sym.index());
return (it != iEffects.end()); }
default: {
Map::const_iterator it = iMap.find(sym.index() | (kind << SHIFT));
return (it != iMap.end()); }
}
}
//! Removes definition for a symbolic attribute from this stylesheet.
/*! This method also works for ESymbol, EGradient, ETiling, and
EEffect. It is okay if the symbolic attribute is not defined in the
stylesheet, nothing happens in this case. */
void StyleSheet::remove(Kind kind, Attribute sym)
{
switch (kind) {
case ETiling:
iTilings.erase(sym.index());
break;
case ESymbol:
iSymbols.erase(sym.index());
break;
case EGradient:
iGradients.erase(sym.index());
break;
case EEffect:
iEffects.erase(sym.index());
break;
default:
iMap.erase(sym.index() | (kind << SHIFT));
break;
};
}
// --------------------------------------------------------------------
//! Return all symbolic names in the style sheet cascade.
/*! Names are simply appended from top to bottom of the cascade.
Names are inserted only once. */
void StyleSheet::allNames(Kind kind, AttributeSeq &seq) const
{
if (kind == ESymbol) {
for (SymbolMap::const_iterator it = iSymbols.begin();
it != iSymbols.end(); ++it) {
Attribute attr(true, it->first);
if (std::find(seq.begin(), seq.end(), attr) == seq.end())
seq.push_back(attr);
}
} else if (kind == EGradient) {
for (GradientMap::const_iterator it = iGradients.begin();
it != iGradients.end(); ++it) {
Attribute attr(true, it->first);
if (std::find(seq.begin(), seq.end(), attr) == seq.end())
seq.push_back(attr);
}
} else if (kind == ETiling) {
for (TilingMap::const_iterator it = iTilings.begin();
it != iTilings.end(); ++it) {
Attribute attr(true, it->first);
if (std::find(seq.begin(), seq.end(), attr) == seq.end())
seq.push_back(attr);
}
} else if (kind == EEffect) {
for (EffectMap::const_iterator it = iEffects.begin();
it != iEffects.end(); ++it) {
Attribute attr(true, it->first);
if (std::find(seq.begin(), seq.end(), attr) == seq.end())
seq.push_back(attr);
}
} else {
uint32_t kindMatch = (kind << SHIFT);
for (Map::const_iterator it = iMap.begin(); it != iMap.end(); ++it) {
if (uint32_t(it->first & KINDMASK) == kindMatch) {
Attribute attr(true, (it->first & MASK));
if (std::find(seq.begin(), seq.end(), attr) == seq.end())
seq.push_back(attr);
}
}
}
}
// --------------------------------------------------------------------
//! Save style sheet in XML format.
void StyleSheet::saveAsXml(Stream &stream, bool saveBitmaps) const
{
stream << "\n";
if (saveBitmaps) {
BitmapFinder bm;
for (SymbolMap::const_iterator it = iSymbols.begin();
it != iSymbols.end(); ++it)
it->second.iObject->accept(bm);
if (!bm.iBitmaps.empty()) {
int id = 1;
Bitmap prev;
for (std::vector::iterator it = bm.iBitmaps.begin();
it != bm.iBitmaps.end(); ++it) {
if (!it->equal(prev)) {
it->saveAsXml(stream, id);
it->setObjNum(id);
} else
it->setObjNum(prev.objNum()); // noop if prev == it
prev = *it;
++id;
}
}
}
Repository *rep = Repository::get();
for (SymbolMap::const_iterator it = iSymbols.begin();
it != iSymbols.end(); ++it) {
stream << "toString(it->first) << "\"";
if (it->second.iTransformations == ETransformationsTranslations)
stream << " transformations=\"translations\"";
else if (it->second.iTransformations == ETransformationsRigidMotions)
stream << " transformations=\"rigid\"";
if (it->second.iXForm)
stream << " xform=\"yes\"";
if (it->second.iSnap.size() > 0) {
stream << " snap=\"";
String sep = "";
for (const Vector & pos : it->second.iSnap) {
stream << sep << pos;
sep = " ";
}
stream << "\"";
}
stream << ">\n";
it->second.iObject->saveAsXml(stream, String());
stream << "\n";
}
for (Map::const_iterator it = iMap.begin(); it != iMap.end(); ++it) {
int kind = (it->first >> SHIFT);
int kind1 = (kind == ELabelStyle) ? ETextStyle : kind;
stream << "<" << kind_names[kind1]
<< " name=\"" << rep->toString(it->first & MASK) << "\"";
if (kind1 == ETextStyle) {
String s = it->second.string();
int i = s.find('\0');
if (kind == ELabelStyle)
stream << " type=\"label\"";
stream << " begin=\"" << s.substr(0, i) << "\" end=\""
<< s.substr(i+1) << "\"/>\n";
} else
stream << " value=\"" << it->second.string() << "\"/>\n";
}
if (!iPreamble.empty()) {
stream << "";
stream.putXmlString(iPreamble);
stream << "\n";
}
if (!iLayout.isNull()) {
stream << " 0.0)
stream << "\" skip=\"" << iLayout.iParagraphSkip;
if (!iLayout.iCrop)
stream << "\" crop=\"no";
stream << "\"/>\n";
}
if (iTextPadding.iLeft >= 0.0) {
stream << "\n";
}
if (iPageNumberStyle.iDefined) {
stream << "" << iPageNumberStyle.iText << "\n";
}
if (iTitleStyle.iDefined) {
stream << "\n";
}
if (iLineCap != EDefaultCap || iLineJoin != EDefaultJoin
|| iFillRule != EDefaultRule) {
stream << "\n";
}
for (GradientMap::const_iterator it = iGradients.begin();
it != iGradients.end(); ++it) {
stream << "toString(it->first) << "\"";
const Gradient &g = it->second;
if (g.iType == Gradient::EAxial)
stream << " type=\"axial\" coords=\""
<< g.iV[0] << " " << g.iV[1] << "\"";
else
stream << " type=\"radial\" coords=\""
<< g.iV[0] << " " << g.iRadius[0] << " "
<< g.iV[1] << " " << g.iRadius[1] << "\"";
if (g.iExtend)
stream << " extend=\"yes\"";
if (!g.iMatrix.isIdentity())
stream << " matrix=\"" << g.iMatrix << "\"";
stream << ">\n";
for (int i = 0; i < size(g.iStops); ++i)
stream << " \n";
stream << "\n";
}
for (TilingMap::const_iterator it = iTilings.begin();
it != iTilings.end(); ++it) {
const Tiling &t = it->second;
stream << "toString(it->first) << "\""
<< " angle=\"" << t.iAngle.degrees() << "\""
<< " step=\"" << t.iStep << "\""
<< " width=\"" << t.iWidth << "\"/>\n";
}
for (EffectMap::const_iterator it = iEffects.begin();
it != iEffects.end(); ++it) {
stream << "toString(it->first) << "\"";
const Effect &e = it->second;
if (e.iDuration != 0)
stream << " duration=\"" << e.iDuration << "\"";
if (e.iTransitionTime != 1)
stream << " transition=\"" << e.iTransitionTime << "\"";
stream << " effect=\"" << int(e.iEffect) << "\"/>\n";
}
stream << "\n";
}
// --------------------------------------------------------------------
/*! \class ipe::Cascade
\ingroup doc
\brief A cascade of style sheets.
The StyleSheets of a document cascade in the sense that a document
can refer to several StyleSheets, which are arranged in a stack. A
lookup is done from top to bottom, and returns as soon as a match is
found. Ipe always appends the built-in "standard" style sheet at the
bottom of the cascade.
*/
//! Create an empty cascade.
/*! This does not add the standard style sheet. */
Cascade::Cascade()
{
// nothing
}
static void destruct_sheets(std::vector &sheets)
{
for (int i = 0; i < size(sheets); ++i) {
delete sheets[i];
sheets[i] = nullptr;
}
sheets.clear();
}
//! Copy constructor.
Cascade::Cascade(const Cascade &rhs)
{
destruct_sheets(iSheets);
for (int i = 0; i < rhs.count(); ++i)
iSheets.push_back(new StyleSheet(*rhs.iSheets[i]));
}
//! Assignment operator.
Cascade &Cascade::operator=(const Cascade &rhs)
{
if (this != &rhs) {
destruct_sheets(iSheets);
for (int i = 0; i < rhs.count(); ++i)
iSheets.push_back(new StyleSheet(*rhs.iSheets[i]));
}
return *this;
}
//! Destructor.
Cascade::~Cascade()
{
destruct_sheets(iSheets);
}
//! Insert a style sheet into the cascade.
/*! Takes ownership of \a sheet. */
void Cascade::insert(int index, StyleSheet *sheet)
{
iSheets.insert(iSheets.begin() + index, sheet);
}
//! Remove a style sheet from the cascade.
/*! The old sheet is deleted. */
void Cascade::remove(int index)
{
iSheets.erase(iSheets.begin() + index);
}
void Cascade::saveAsXml(Stream &stream) const
{
for (int i = count() - 1; i >= 0; --i) {
if (!iSheets[i]->isStandard())
iSheets[i]->saveAsXml(stream);
}
}
bool Cascade::has(Kind kind, Attribute sym) const
{
for (int i = 0; i < count(); ++i) {
if (iSheets[i]->has(kind, sym))
return true;
}
return false;
}
Attribute Cascade::find(Kind kind, Attribute sym) const
{
for (int i = 0; i < count(); ++i) {
Attribute a = iSheets[i]->find(kind, sym);
if (a != Attribute::UNDEFINED())
return a;
}
Attribute normal = Attribute::normal(kind);
for (int i = 0; i < count(); ++i) {
Attribute a = iSheets[i]->find(kind, normal);
if (a != Attribute::UNDEFINED())
return a;
}
// this shouldn't happen
return Attribute::UNDEFINED();
}
const Symbol *Cascade::findSymbol(Attribute sym) const
{
for (int i = 0; i < count(); ++i) {
const Symbol *s = iSheets[i]->findSymbol(sym);
if (s) return s;
}
return nullptr;
}
const Gradient *Cascade::findGradient(Attribute sym) const
{
for (int i = 0; i < count(); ++i) {
const Gradient *s = iSheets[i]->findGradient(sym);
if (s) return s;
}
return nullptr;
}
const Tiling *Cascade::findTiling(Attribute sym) const
{
for (int i = 0; i < count(); ++i) {
const Tiling *s = iSheets[i]->findTiling(sym);
if (s) return s;
}
return nullptr;
}
const Effect *Cascade::findEffect(Attribute sym) const
{
for (int i = 0; i < count(); ++i) {
const Effect *s = iSheets[i]->findEffect(sym);
if (s) return s;
}
return nullptr;
}
//! Find page layout (such as text margins).
const Layout *Cascade::findLayout() const
{
for (int i = 0; i < count(); ++i) {
const Layout *l = iSheets[i]->layout();
if (l) return l;
}
// must never happen
assert(false);
return nullptr;
}
//! Find text padding (for text bbox computation).
const TextPadding *Cascade::findTextPadding() const
{
for (int i = 0; i < count(); ++i) {
const TextPadding *t = iSheets[i]->textPadding();
if (t) return t;
}
// must never happen
assert(false);
return nullptr;
}
//! Get style of page titles (or 0 if none defined).
const StyleSheet::TitleStyle *Cascade::findTitleStyle() const
{
for (int i = 0; i < count(); ++i) {
const StyleSheet::TitleStyle *ts = iSheets[i]->titleStyle();
if (ts) return ts;
}
return nullptr;
}
//! Return style of page numbering (or 0 if none defined).
const StyleSheet::PageNumberStyle *Cascade::findPageNumberStyle() const
{
for (int i = 0; i < count(); ++i) {
const StyleSheet::PageNumberStyle *pns = iSheets[i]->pageNumberStyle();
if (pns) return pns;
}
return nullptr;
}
//! Return total LaTeX preamble (of the whole cascade).
String Cascade::findPreamble() const
{
String s;
for (int i = 0; i < count(); ++i) {
s = iSheets[i]->preamble() + "\n" + s;
}
return s;
}
TLineCap Cascade::lineCap() const
{
for (int i = 0; i < count(); ++i) {
if (iSheets[i]->lineCap() != EDefaultCap)
return iSheets[i]->lineCap();
}
return EButtCap; // should not happen
}
TLineJoin Cascade::lineJoin() const
{
for (int i = 0; i < count(); ++i) {
if (iSheets[i]->lineJoin() != EDefaultJoin)
return iSheets[i]->lineJoin();
}
return ERoundJoin; // should not happen
}
TFillRule Cascade::fillRule() const
{
for (int i = 0; i < count(); ++i) {
if (iSheets[i]->fillRule() != EDefaultRule)
return iSheets[i]->fillRule();
}
return EEvenOddRule; // should not happen
}
void Cascade::allNames(Kind kind, AttributeSeq &seq) const
{
if (has(kind, Attribute::NORMAL()))
seq.push_back(Attribute::NORMAL());
for (int i = 0; i < count(); ++i)
iSheets[i]->allNames(kind, seq);
}
//! Find stylesheet defining the attribute.
/*! This method goes through the cascade looking for a definition of
the symbolic attribute \a sym. It returns the index of the
stylesheet defining the attribute, or -1 if the attribute is not
defined.
The method panics if \a sym is not symbolic. It also works for
ESymbol, EGradient, ETiling, and EEffect.
*/
int Cascade::findDefinition(Kind kind, Attribute sym) const
{
assert(sym.isSymbolic());
for (int i = 0; i < count(); ++i) {
if (iSheets[i]->has(kind, sym))
return i;
}
return -1;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipedoc.cpp 0000644 0001750 0001750 00000055104 13561570220 016243 0 ustar otfried otfried // --------------------------------------------------------------------
// The Ipe document.
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipedoc.h"
#include "ipeiml.h"
#include "ipestyle.h"
#include "ipereference.h"
#include "ipepainter.h"
#include "ipeutils.h"
#include "ipepdfparser.h"
#include "ipepdfwriter.h"
#include "ipelatex.h"
#include
using namespace ipe;
// --------------------------------------------------------------------
/*! \defgroup doc Ipe Document
\brief The classes managing an Ipe document.
The main class, Document, represents an entire Ipe document, and
allows you to load, save, access, and modify such a document.
Other classes represent pages, layers, and views of a document.
Another important class is the StyleSheet, which maps symbolic
attributes to absolute values.
*/
/*! \class ipe::Document
\ingroup doc
\brief The model for an Ipe document.
The Document class represents the contents of an Ipe document, and
all the methods necessary to load, save, and modify it.
*/
//! Construct an empty document for filling by a client.
/*! As constructed, it has no pages, A4 media, and
only the standard style sheet. */
Document::Document()
{
iResources = nullptr;
iCascade = new Cascade();
iCascade->insert(0, StyleSheet::standard());
}
//! Destructor.
Document::~Document()
{
for (int i = 0; i < countPages(); ++i)
delete page(i);
delete iCascade;
delete iResources;
}
//! Copy constructor.
Document::Document(const Document &rhs)
{
iCascade = new Cascade(*rhs.iCascade);
for (int i = 0; i < rhs.countPages(); ++i)
iPages.push_back(new Page(*rhs.page(i)));
iProperties = rhs.iProperties;
iResources = nullptr;
}
// ---------------------------------------------------------------------
String readLine(DataSource &source)
{
String s;
int ch = source.getChar();
while (ch != EOF && ch != '\n') {
s += char(ch);
ch = source.getChar();
}
return s;
}
//! Determine format of file in \a source.
FileFormat Document::fileFormat(DataSource &source)
{
String s1 = readLine(source);
String s2 = readLine(source);
if (s1.substr(0, 5) == "dict() || obj->dict()->stream().size() == 0)
return Buffer();
return obj->dict()->stream();
}
// --------------------------------------------------------------------
class PsSource : public DataSource {
public:
PsSource(DataSource &source) : iSource(source) { /* nothing */ }
bool skipToXml();
String readLine();
Buffer image(int index) const;
int getNext() const;
inline bool deflated() const { return iDeflated; }
virtual int getChar();
private:
DataSource &iSource;
std::vector iImages;
bool iEos;
bool iDeflated;
};
int PsSource::getChar()
{
int ch = iSource.getChar();
if (ch == '\n')
iSource.getChar(); // remove '%'
return ch;
}
String PsSource::readLine()
{
String s;
int ch = iSource.getChar();
while (ch != EOF && ch != '\n') {
s += char(ch);
ch = iSource.getChar();
}
iEos = (ch == EOF);
return s;
}
Buffer PsSource::image(int index) const
{
if (1 <= index && index <= int(iImages.size()))
return iImages[index - 1];
else
return Buffer();
}
bool PsSource::skipToXml()
{
iDeflated = false;
String s1 = readLine();
String s2 = readLine();
if (s1.substr(0, 11) != "%!PS-Adobe-" ||
s2.substr(0, 17) != "%%Creator: Ipelib")
return false;
do {
s1 = readLine();
if (s1.substr(0, 17) == "%%BeginIpeImage: ") {
Lex lex(s1.substr(17));
int num, len;
lex >> num >> len;
if (num != int(iImages.size() + 1))
return false;
(void) readLine(); // skip 'image'
Buffer buf(len);
A85Source a85(iSource);
char *p = buf.data();
char *p1 = p + buf.size();
while (p < p1) {
int ch = a85.getChar();
if (ch == EOF)
return false;
*p++ = char(ch);
}
iImages.push_back(buf);
}
} while (!iEos && s1.substr(0, 13) != "%%BeginIpeXml");
iDeflated = (s1.substr(13, 14) == ": /FlateDecode");
if (iEos)
return false;
(void) iSource.getChar(); // skip '%' before
return true;
}
class PsStreamParser : public ImlParser {
public:
explicit PsStreamParser(DataSource &source, PsSource &psSource);
virtual Buffer pdfStream(int objNum);
private:
PsSource &iPsSource;
};
PsStreamParser::PsStreamParser(DataSource &source, PsSource &psSource)
: ImlParser(source), iPsSource(psSource)
{
// nothing
}
Buffer PsStreamParser::pdfStream(int objNum)
{
return iPsSource.image(objNum);
}
// --------------------------------------------------------------------
Document *doParse(Document *self, ImlParser &parser, int &reason)
{
int res = parser.parseDocument(*self);
if (res) {
delete self;
self = nullptr;
if (res == ImlParser::ESyntaxError)
reason = parser.parsePosition();
else
reason = -res;
}
return self;
}
Document *doParseXml(DataSource &source, int &reason)
{
Document *self = new Document;
ImlParser parser(source);
return doParse(self, parser, reason);
}
Document *doParsePs(DataSource &source, int &reason)
{
PsSource psSource(source);
reason = Document::EFileOpenError; // could not find Xml stream
if (!psSource.skipToXml())
return nullptr;
Document *self = new Document;
if (psSource.deflated()) {
A85Source a85(psSource);
InflateSource source(a85);
PsStreamParser parser(source, psSource);
return doParse(self, parser, reason);
} else {
PsStreamParser parser(psSource, psSource);
return doParse(self, parser, reason);
}
}
Document *doParsePdf(DataSource &source, int &reason)
{
PdfFile loader;
reason = Document::ENotAnIpeFile;
if (!loader.parse(source)) // could not parse PDF container
return nullptr;
const PdfObj *obj = loader.catalog()->get("PieceInfo", &loader);
if (obj && obj->dict()) {
obj = obj->dict()->get("Ipe", &loader);
if (obj && obj->dict())
obj = obj->dict()->get("Private", &loader);
}
if (!obj)
obj = loader.object(1);
// was the object really created by Ipe?
if (!obj || !obj->dict())
return nullptr;
const PdfObj *type = obj->dict()->get("Type", nullptr);
if (!type || !type->name() || type->name()->value() != "Ipe")
return nullptr;
Buffer buffer = obj->dict()->stream();
BufferSource xml(buffer);
Document *self = new Document;
if (obj->dict()->deflated()) {
InflateSource xml1(xml);
PdfStreamParser parser(loader, xml1);
return doParse(self, parser, reason);
} else {
PdfStreamParser parser(loader, xml);
return doParse(self, parser, reason);
}
}
//! Construct a document from an input stream.
/*! Returns 0 if the stream couldn't be parsed, and a reason
explaining that in \a reason. If \a reason is positive, it is a
file (stream) offset where parsing failed. If \a reason is
negative, it is an error code, see Document::LoadErrors.
*/
Document *Document::load(DataSource &source, FileFormat format,
int &reason)
{
if (format == FileFormat::Xml)
return doParseXml(source, reason);
if (format == FileFormat::Pdf)
return doParsePdf(source, reason);
if (format == FileFormat::Eps)
return doParsePs(source, reason);
reason = (format == FileFormat::Ipe5) ? EVersionTooOld : ENotAnIpeFile;
return nullptr;
}
Document *Document::load(const char *fname, int &reason)
{
reason = EFileOpenError;
std::FILE *fd = Platform::fopen(fname, "rb");
if (!fd)
return nullptr;
FileSource source(fd);
FileFormat format = fileFormat(source);
std::rewind(fd);
Document *self = load(source, format, reason);
std::fclose(fd);
return self;
}
Document *Document::loadWithErrorReport(const char *fname)
{
int reason;
Document *doc = load(fname, reason);
if (doc)
return doc;
fprintf(stderr, "Could not read Ipe file '%s'\n", fname);
switch (reason) {
case Document::EVersionTooOld:
fprintf(stderr, "The Ipe version of this document is too old.\n"
"Please convert it using 'ipe6upgrade'.\n");
break;
case Document::EVersionTooRecent:
fprintf(stderr, "The document was created by a newer version of Ipe.\n"
"Please upgrade your Ipe installation.\n");
break;
case Document::EFileOpenError:
perror("Error opening the file");
break;
case Document::ENotAnIpeFile:
fprintf(stderr, "The document was not created by Ipe.\n");
break;
default:
fprintf(stderr, "Error parsing the document at position %d\n.", reason);
break;
}
return nullptr;
}
// --------------------------------------------------------------------
//! Save in a stream.
/*! Returns true if sucessful.
*/
bool Document::save(TellStream &stream, FileFormat format, uint32_t flags) const
{
if (format == FileFormat::Xml) {
stream << "\n";
stream << "\n";
saveAsXml(stream);
return true;
}
int compresslevel = 9;
if (flags & SaveFlag::NoZip)
compresslevel = 0;
if (format == FileFormat::Pdf) {
PdfWriter writer(stream, this, iResources, flags, 0, -1, compresslevel);
writer.createPages();
writer.createBookmarks();
writer.createNamedDests();
if (!(flags & SaveFlag::Export)) {
String xmlData;
StringStream stream(xmlData);
if (compresslevel > 0) {
DeflateStream dfStream(stream, compresslevel);
// all bitmaps have been embedded and carry correct object number
saveAsXml(dfStream, true);
dfStream.close();
writer.createXmlStream(xmlData, true);
} else {
saveAsXml(stream, true);
writer.createXmlStream(xmlData, false);
}
}
writer.createTrailer();
return true;
}
return false;
}
bool Document::save(const char *fname, FileFormat format, uint32_t flags) const
{
std::FILE *fd = Platform::fopen(fname, "wb");
if (!fd)
return false;
FileStream stream(fd);
bool result = save(stream, format, flags);
std::fclose(fd);
return result;
}
//! Export a single view to PDF
bool Document::exportView(const char *fname, FileFormat format, uint32_t flags,
int pno, int vno) const
{
if (format != FileFormat::Pdf)
return false;
int compresslevel = 9;
if (flags & SaveFlag::NoZip)
compresslevel = 0;
std::FILE *fd = Platform::fopen(fname, "wb");
if (!fd)
return false;
FileStream stream(fd);
PdfWriter writer(stream, this, iResources, flags, pno, pno, compresslevel);
writer.createPageView(pno, vno);
writer.createTrailer();
std::fclose(fd);
return true;
}
//! Export a range of pages to PDF.
bool Document::exportPages(const char *fname, uint32_t flags,
int fromPage, int toPage) const
{
int compresslevel = 9;
if (flags & SaveFlag::NoZip)
compresslevel = 0;
std::FILE *fd = Platform::fopen(fname, "wb");
if (!fd)
return false;
FileStream stream(fd);
PdfWriter writer(stream, this, iResources, flags, fromPage, toPage, compresslevel);
writer.createPages();
writer.createTrailer();
std::fclose(fd);
return true;
}
// --------------------------------------------------------------------
//! Create a list of all bitmaps in the document.
void Document::findBitmaps(BitmapFinder &bm) const
{
for (int i = 0; i < countPages(); ++i)
bm.scanPage(page(i));
// also need to look at all templates
AttributeSeq seq;
iCascade->allNames(ESymbol, seq);
for (AttributeSeq::iterator it = seq.begin(); it != seq.end(); ++it) {
const Symbol *symbol = iCascade->findSymbol(*it);
symbol->iObject->accept(bm);
}
std::sort(bm.iBitmaps.begin(), bm.iBitmaps.end());
}
//! Save in XML format into an Stream.
void Document::saveAsXml(Stream &stream, bool usePdfBitmaps) const
{
stream << "\n";
String info;
StringStream infoStr(info);
infoStr << "\n";
if (info.size() > 10)
stream << info;
if (!iProperties.iPreamble.empty()) {
stream << "";
stream.putXmlString(iProperties.iPreamble);
stream << "\n";
}
// save bitmaps
BitmapFinder bm;
findBitmaps(bm);
if (!bm.iBitmaps.empty()) {
int id = 1;
Bitmap prev;
for (std::vector::iterator it = bm.iBitmaps.begin();
it != bm.iBitmaps.end(); ++it) {
if (!it->equal(prev)) {
if (usePdfBitmaps) {
it->saveAsXml(stream, it->objNum(), it->objNum());
} else {
it->saveAsXml(stream, id);
it->setObjNum(id);
}
} else
it->setObjNum(prev.objNum()); // noop if prev == it
prev = *it;
++id;
}
}
// now save style sheet
iCascade->saveAsXml(stream);
// save pages
for (int i = 0; i < countPages(); ++i)
page(i)->saveAsXml(stream);
stream << "\n";
}
// --------------------------------------------------------------------
//! Set document properties.
void Document::setProperties(const SProperties &props)
{
iProperties = props;
}
//! Replace the entire style sheet cascade.
/*! Takes ownership of \a cascade, and returns the original cascade. */
Cascade *Document::replaceCascade(Cascade *sheets)
{
Cascade *old = iCascade;
iCascade = sheets;
return old;
}
//! Check all symbolic attributes in the document.
/*! This function verifies that all symbolic attributes in the
document are defined in the style sheet. It appends to \a seq all
symbolic attributes (in no particular order, but without duplicates)
that are NOT defined.
Returns \c true if there are no undefined symbolic attributes in the
document.
*/
bool Document::checkStyle(AttributeSeq &seq) const
{
for (int i = 0; i < countPages(); ++i) {
for (int j = 0; j < page(i)->count(); ++j) {
page(i)->object(j)->checkStyle(cascade(), seq);
}
}
return (seq.size() == 0);
}
//! Update the PDF resources (after running latex).
/*! Takes ownership. */
void Document::setResources(PdfResources *resources)
{
delete iResources;
iResources = resources;
}
#if 0
//! Load a style sheet and add at top of cascade.
bool Document::addStyleSheet(DataSource &source)
{
ImlParser parser(source);
StyleSheet *sheet = parser.parseStyleSheet();
if (sheet) {
sheet->setCascade(iCascade);
setStyleSheet(sheet);
return true;
} else
return false;
}
#endif
//! Return total number of views in all pages.
int Document::countTotalViews() const
{
int views = 0;
for (int i = 0; i < countPages(); ++i) {
int nviews = page(i)->countViews();
views += (nviews > 0) ? nviews : 1;
}
return views;
}
//! Return page index given a section title or page number.
/*! Input page numbers are 1-based strings.
Returns -1 if page not found. */
int Document::findPage(String s) const
{
if (s.empty())
return -1;
if ('0' <= s[0] && s[0] <= '9') {
int no = Lex(s).getInt();
if (no <= 0 || no > countPages())
return -1;
return no - 1;
}
for (int i = 0; i < countPages(); ++i) {
if (s == page(i)->section(0))
return i;
}
return -1;
}
//! Insert a new page.
/*! The page is inserted at index \a no. */
void Document::insert(int no, Page *page)
{
iPages.insert(iPages.begin() + no, page);
}
//! Append a new page.
void Document::push_back(Page *page)
{
iPages.push_back(page);
}
//! Replace page.
/*! Returns the original page. */
Page *Document::set(int no, Page *page)
{
Page *p = iPages[no];
iPages[no] = page;
return p;
}
//! Remove a page.
/*! Returns the page that has been removed. */
Page *Document::remove(int no)
{
Page *p = iPages[no];
iPages.erase(iPages.begin() + no);
return p;
}
// --------------------------------------------------------------------
//! Run PdfLatex or Xelatex
int Document::runLatex(String docname, String &texLog)
{
texLog = "";
Latex converter(cascade(), iProperties.iTexEngine);
AttributeSeq seq;
cascade()->allNames(ESymbol, seq);
for (AttributeSeq::iterator it = seq.begin(); it != seq.end(); ++it) {
const Symbol *sym = cascade()->findSymbol(*it);
if (sym)
converter.scanObject(sym->iObject);
}
int count = 0;
for (int i = 0; i < countPages(); ++i)
count = converter.scanPage(page(i));
if (count == 0)
return ErrNoText;
if (iProperties.iNumberPages) {
for (int i = 0; i < countPages(); ++i) {
int nviews = page(i)->countViews();
for (int j = 0; j < nviews; ++j)
converter.addPageNumber(i, j, countPages(), nviews);
}
}
// First we need a directory
String latexDir = Platform::latexDirectory();
if (latexDir.empty())
return ErrNoDir;
String texFile = latexDir + "ipetemp.tex";
String pdfFile = latexDir + "ipetemp.pdf";
String logFile = latexDir + "ipetemp.log";
std::remove(logFile.z());
std::FILE *file = Platform::fopen(texFile.z(), "wb");
if (!file)
return ErrWritingSource;
FileStream stream(file);
int err = converter.createLatexSource(stream, properties().iPreamble);
std::fclose(file);
if (err < 0)
return ErrWritingSource;
int result = Platform::runLatex(latexDir, iProperties.iTexEngine, docname);
if (result != 0 && result != 1)
return ErrRunLatex;
// Check log file for Pdflatex version and errors
texLog = Platform::readFile(logFile);
if (texLog.left(14) != "This is pdfTeX" &&
texLog.left(15) != "This is pdfeTeX" &&
texLog.left(13) != "This is XeTeX" &&
texLog.left(14) != "This is LuaTeX" &&
texLog.left(22) != "entering extended mode")
return ErrRunLatex;
int i = texLog.find('\n');
if (i < 0)
return ErrRunLatex;
String version = texLog.substr(8, i);
ipeDebug("%s", version.z());
// Check for error
if (texLog.find("\n!") >= 0)
return ErrLatex;
std::FILE *pdfF = Platform::fopen(pdfFile.z(), "rb");
if (!pdfF)
return ErrLatex;
FileSource source(pdfF);
bool okay = (converter.readPdf(source) && converter.updateTextObjects());
std::fclose(pdfF);
if (okay) {
setResources(converter.takeResources());
// resources()->show();
return ErrNone;
} else
return ErrLatexOutput;
}
//! Run Pdflatex (suitable for console applications)
/*! Success/error is reported on stderr. */
int Document::runLatex(String docname)
{
String logFile;
switch (runLatex(docname, logFile)) {
case ErrNoText:
fprintf(stderr, "No text objects in document, no need to run Pdflatex.\n");
return 0;
case ErrNoDir:
fprintf(stderr, "Directory '%s' does not exist and cannot be created.\n",
"latexdir");
return 1;
case ErrWritingSource:
fprintf(stderr, "Error writing Latex source.\n");
return 1;
case ErrOldPdfLatex:
fprintf(stderr, "Your installed version of Pdflatex is too old.\n");
return 1;
case ErrRunLatex:
fprintf(stderr, "There was an error trying to run Pdflatex.\n");
return 1;
case ErrLatex:
fprintf(stderr, "There were Latex errors.\n");
return 1;
case ErrLatexOutput:
fprintf(stderr, "There was an error reading the Pdflatex output.\n");
return 1;
case ErrNone:
default:
fprintf(stderr, "Pdflatex was run sucessfully.\n");
return 0;
}
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipesnap.cpp 0000644 0001750 0001750 00000027316 13561570220 016443 0 ustar otfried otfried // --------------------------------------------------------------------
// Snapping
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipesnap.h"
#include "ipepage.h"
#include "ipegroup.h"
#include "ipereference.h"
#include "ipepath.h"
using namespace ipe;
/*! \defgroup high Ipe Utilities
\brief Classes to manage Ipe documents and objects.
This module contains classes used in the implementation of the Ipe
program itself. The only classes from this module you may be
interested in are Visitor (which is essential to traverse an Ipe
object structure), and perhaps Snap (if you are writing an Ipelet
whose behavior depends on the current snap setting in the Ipe
program).
*/
/*! \class ipe::Snap
\ingroup high
\brief Performs snapping operations, and stores snapping state.
*/
// --------------------------------------------------------------------
class CollectSegs : public Visitor {
public:
CollectSegs(const Vector &mouse, double snapDist,
const Page *page, int view);
virtual void visitGroup(const Group *obj);
virtual void visitPath(const Path *obj);
public:
std::vector iSegs;
std::vector iBeziers;
std::vector iBeziersCont; // true if continuation of previous bezier
std::vector iArcs;
private:
std::vector iMatrices;
Vector iMouse;
double iDist;
int iView;
};
CollectSegs::CollectSegs(const Vector &mouse, double snapDist,
const Page *page, int view)
: iMouse(mouse), iDist(snapDist), iView(view)
{
iMatrices.push_back(Matrix()); // identity matrix
for (int i = 0; i < page->count(); ++i) {
if (page->objSnapsInView(i, iView))
page->object(i)->accept(*this);
}
}
void CollectSegs::visitGroup(const Group *obj)
{
iMatrices.push_back(iMatrices.back() * obj->matrix());
for (Group::const_iterator it = obj->begin(); it != obj->end(); ++it)
(*it)->accept(*this);
iMatrices.pop_back();
}
// TODO: use bounding boxes for subsegs/beziers to speed up ?
void CollectSegs::visitPath(const Path *obj)
{
Bezier b;
Arc arc;
Matrix m = iMatrices.back() * obj->matrix();
for (int i = 0; i < obj->shape().countSubPaths(); ++i) {
const SubPath *sp = obj->shape().subPath(i);
switch (sp->type()) {
case SubPath::EEllipse:
if (sp->distance(iMouse, m, iDist) < iDist)
iArcs.push_back(m * Arc(sp->asEllipse()->matrix()));
break;
case SubPath::EClosedSpline: {
std::vector bez;
bool cont = false;
sp->asClosedSpline()->beziers(bez);
for (const Bezier & bz : bez) {
b = m * bz;
if (b.distance(iMouse, iDist) < iDist) {
iBeziers.push_back(b);
iBeziersCont.push_back(cont);
cont = true;
} else
cont = false;
}
break; }
case SubPath::ECurve: {
const Curve *ssp = sp->asCurve();
int ns = ssp->closed() ? -1 : 0;
Vector u[2];
for (int j = ns; j < ssp->countSegments(); ++j) {
CurveSegment seg = j < 0 ? ssp->closingSegment(u) : ssp->segment(j);
switch (seg.type()) {
case CurveSegment::ESegment:
if (seg.distance(iMouse, m, iDist) < iDist)
iSegs.push_back(Segment(m * seg.cp(0), m * seg.cp(1)));
break;
case CurveSegment::EArc:
arc = m * seg.arc();
if (arc.distance(iMouse, iDist) < iDist)
iArcs.push_back(arc);
break;
case CurveSegment::EOldSpline:
case CurveSegment::ESpline: {
std::vector bez;
seg.beziers(bez);
bool cont = false;
for (const Bezier & bz : bez) {
b = m * bz;
if (b.distance(iMouse, iDist) < iDist) {
iBeziers.push_back(b);
iBeziersCont.push_back(cont);
cont = true;
} else
cont = false;
}
break; }
}
}
break; }
}
}
}
// --------------------------------------------------------------------
/*! Find line through \a base with slope determined by angular snap
size and direction. */
Line Snap::getLine(const Vector &mouse, const Vector &base) const noexcept
{
Angle alpha = iDir;
Vector d = mouse - base;
if (d.len() > 2.0) {
alpha = d.angle() - iDir;
alpha.normalize(0.0);
alpha = iAngleSize * int(alpha / iAngleSize + 0.5) + iDir;
}
return Line(base, Vector(alpha));
}
//! Perform intersection snapping.
void Snap::intersectionSnap(const Vector &pos, Vector &fifi,
const Page *page, int view,
double &snapDist) const noexcept
{
CollectSegs segs(pos, snapDist, page, view);
Vector v;
std::vector pts;
// 1. seg-seg intersections
for (int i = 0; i < size(segs.iSegs); ++i) {
for (int j = i + 1; j < size(segs.iSegs); ++j) {
if (segs.iSegs[i].intersects(segs.iSegs[j], v))
pts.push_back(v);
}
}
// 2. bezier-bezier and bezier-seg intersections
for (int i = 0; i < size(segs.iBeziers); ++i) {
for (int j = i + 1; j < size(segs.iBeziers); ++j) {
if (j > i+1 || !segs.iBeziersCont[j])
segs.iBeziers[i].intersect(segs.iBeziers[j], pts);
}
for (int j = 0; j < size(segs.iSegs); ++j)
segs.iBeziers[i].intersect(segs.iSegs[j], pts);
}
// 3. arc-arc, arc-bezier, and arc-segment intersections
for (int i = 0; i < size(segs.iArcs); ++i) {
for (int j = i+1; j < size(segs.iArcs); ++j)
segs.iArcs[i].intersect(segs.iArcs[j], pts);
for (int j = 0; j < size(segs.iBeziers); ++j)
segs.iArcs[i].intersect(segs.iBeziers[j], pts);
for (int j = 0; j < size(segs.iSegs); ++j)
segs.iArcs[i].intersect(segs.iSegs[j], pts);
}
double d = snapDist;
Vector pos1 = pos;
double d1;
for (const auto & pt : pts) {
if ((d1 = (pos - pt).len()) < d) {
d = d1;
pos1 = pt;
}
}
if (d < snapDist) {
fifi = pos1;
snapDist = d;
}
}
//! Perform snapping to intersection of angular line and pos.
bool Snap::snapAngularIntersection(Vector &pos, const Line &l,
const Page *page, int view,
double snapDist) const noexcept
{
CollectSegs segs(pos, snapDist, page, view);
std::vector pts;
Vector v;
for (std::vector::const_iterator it = segs.iSegs.begin();
it != segs.iSegs.end(); ++it) {
if (it->intersects(l, v))
pts.push_back(v);
}
for (std::vector::const_iterator it = segs.iArcs.begin();
it != segs.iArcs.end(); ++it) {
it->intersect(l, pts);
}
for (std::vector::const_iterator it = segs.iBeziers.begin();
it != segs.iBeziers.end(); ++it) {
it->intersect(l, pts);
}
double d = snapDist;
Vector pos1 = pos;
double d1;
for (const auto & pt : pts) {
if ((d1 = (pos - pt).len()) < d) {
d = d1;
pos1 = pt;
}
}
if (d < snapDist) {
pos = pos1;
return true;
}
return false;
}
//! Tries vertex, intersection, boundary, and grid snapping.
/*! If snapping occurred, \a pos is set to the new user space position. */
Snap::TSnapModes Snap::simpleSnap(Vector &pos, const Page *page, int view,
double snapDist, Tool *tool) const noexcept
{
double d = snapDist;
Vector fifi = pos;
// highest priority: vertex snapping
if (iSnap & ESnapVtx) {
for (int i = 0; i < page->count(); ++i) {
if (page->objSnapsInView(i, view))
page->snapVtx(i, pos, fifi, d);
}
if (tool)
tool->snapVtx(pos, fifi, d, false);
}
double dvtx = d;
Vector fifiCtl = pos;
if (iSnap & ESnapCtl) {
for (int i = 0; i < page->count(); ++i) {
if (page->objSnapsInView(i, view))
page->snapCtl(i, pos, fifiCtl, d);
}
if (tool)
tool->snapVtx(pos, fifiCtl, d, true);
}
double dctl = d;
Vector fifiX = pos;
if (iSnap & ESnapInt)
intersectionSnap(pos, fifiX, page, view, d);
// Return if snapping has occurred
if (d < dctl) {
pos = fifiX;
return ESnapInt;
} else if (d < dvtx) {
pos = fifiCtl;
return ESnapCtl;
} else if (d < snapDist) {
pos = fifi;
return ESnapVtx;
}
// boundary snapping
if (iSnap & ESnapBd) {
for (int i = 0; i < page->count(); ++i) {
if (page->objSnapsInView(i, view))
page->snapBnd(i, pos, fifi, d);
}
if (d < snapDist) {
pos = fifi;
return ESnapBd;
}
}
// grid snapping: always occurs
if (iSnap & ESnapGrid) {
int grid = iGridSize;
fifi.x = grid * int(pos.x / grid + (pos.x > 0 ? 0.5 : -0.5));
fifi.y = grid * int(pos.y / grid + (pos.y > 0 ? 0.5 : -0.5));
pos = fifi;
return ESnapGrid;
}
return ESnapNone;
}
//! Performs snapping of position \a pos.
/*! Returns \c true if snapping occurred. In that case \a pos is set
to the new user space position.
Automatic angular snapping occurs if \a autoOrg is not null --- the
value is then used as the origin for automatic angular snapping.
*/
Snap::TSnapModes Snap::snap(Vector &pos, const Page *page, int view,
double snapDist, Tool *tool,
Vector *autoOrg) const noexcept
{
// automatic angular snapping and angular snapping both on?
if (autoOrg && (iSnap & ESnapAuto) && (iSnap & ESnapAngle)) {
// only one possible point!
Line angular = getLine(pos, iOrigin);
Line automat = getLine(pos, *autoOrg);
Vector v;
if (angular.intersects(automat, v) && v.sqLen() < 1e10) {
pos = v;
return ESnapAngle;
}
// if the two lines do not intersect, use following case
}
// case of only one angular snapping mode
if ((iSnap & ESnapAngle) || (autoOrg && (iSnap & ESnapAuto))) {
Vector org;
if (iSnap & ESnapAngle)
org = iOrigin;
else
org = *autoOrg;
Line l = getLine(pos, org);
pos = l.project(pos);
if (iSnap & ESnapBd)
snapAngularIntersection(pos, l, page, view, snapDist);
return ESnapAngle;
}
// we are not in any angular snapping mode
return simpleSnap(pos, page, view, snapDist, tool);
}
//! Set axis origin and direction from edge near mouse.
/*! Returns \c true if successful. */
bool Snap::setEdge(const Vector &pos, const Page *page, int view) noexcept
{
// bound cannot be too small, as distance to Bezier
// is computed based on an approximation of precision 1.0
CollectSegs segs(pos, 2.0, page, view);
if (!segs.iSegs.empty()) {
Segment seg = segs.iSegs.back();
Line l = seg.line();
iOrigin = l.project(pos);
Vector dir = l.dir();
if ((iOrigin - seg.iP).len() > (iOrigin - seg.iQ).len())
dir = -dir;
iDir = dir.angle();
return true;
} else if (!segs.iArcs.empty()) {
Arc arc = segs.iArcs.back();
Angle alpha;
(void) arc.distance(pos, 3.0, iOrigin, alpha);
iDir = (arc.iM.linear() * Vector(Angle(alpha + IpeHalfPi))).angle();
return true;
} else if (!segs.iBeziers.empty()) {
Bezier bez = segs.iBeziers.back();
double t;
double bound = 2.0;
if (!bez.snap(pos, t, iOrigin, bound))
return false;
iDir = bez.tangent(t).angle();
return true;
}
return false;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipepage.cpp 0000644 0001750 0001750 00000050144 13561570220 016411 0 ustar otfried otfried // --------------------------------------------------------------------
// A page of a document.
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipepage.h"
#include "ipegroup.h"
#include "ipereference.h"
#include "ipepainter.h"
#include "ipeiml.h"
#include "ipeutils.h"
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::Page
\ingroup doc
\brief An Ipe document page.
Its main ingredients are a sequence of Objects (with selection
state, layer, and a cached bounding box), a set of Layers, and
a sequence of Views.
Each object on a Page belongs to one of the layers of the page.
Layers are orthogonal to the back-to-front ordering of objects (in
particular, they are not "layered" - the word is a misnomer). The
"layer" is really just another attribute of the object.
- Layers are either editable or locked. Objects in a locked layer
cannot be selected, and a locked layer cannot be made active in
the UI. This more or less means that the contents of such a layer
cannot be modified---but that's a consequence of the UI, Ipelib
contains no special handling of locked layers.
- A layer may have snapping on or off---objects will behave
magnetically only if their layer has snapping on.
A Page is presented in a number of \e views. Each view presents
some of the layers of the page. In addition, each view has an
active layer (where objects are added when this view is shown in the
UI), and possibly a transition effect (Acrobat Reader eye candy).
A Page can be copied and assigned. The operation takes time linear
in the number of top-level object on the page.
*/
//! The default constructor creates a new empty page.
/*! This page still needs a layer and a view to be usable! */
Page::Page() : iTitle()
{
iUseTitle[0] = iUseTitle[1] = false;
iMarked = true;
}
//! Create a new empty page with standard settings.
/*! This is an empty page with layer 'alpha' and a single view. */
Page *Page::basic()
{
// Create one empty page
Page *page = new Page;
page->addLayer("alpha");
page->insertView(0, "alpha");
page->setVisible(0, "alpha", true);
return page;
}
// --------------------------------------------------------------------
//! save page in XML format.
void Page::saveAsXml(Stream &stream) const
{
stream << "\n";
if (!iNotes.empty()) {
stream << "";
stream.putXmlString(iNotes);
stream << "\n";
}
for (int i = 0; i < countLayers(); ++i) {
stream << "\n";
}
for (int i = 0; i < countViews(); ++i) {
stream << "\n";
}
int currentLayer = -1;
for (ObjSeq::const_iterator it = iObjects.begin();
it != iObjects.end(); ++it) {
String l;
if (it->iLayer != currentLayer) {
currentLayer = it->iLayer;
l = layer(currentLayer);
}
it->iObject->saveAsXml(stream, l);
}
stream << "\n";
}
// --------------------------------------------------------------------
Page::SLayer::SLayer(String name)
{
iName = name;
iFlags = 0;
}
//! Set locking of layer \a i.
void Page::setLocked(int i, bool flag)
{
iLayers[i].iFlags &= ~ELocked;
if (flag) iLayers[i].iFlags |= ELocked;
}
//! Set snapping of layer \a i.
void Page::setSnapping(int i, bool flag)
{
iLayers[i].iFlags &= ~ENoSnapping;
if (!flag) iLayers[i].iFlags |= ENoSnapping;
}
//! Add a new layer.
void Page::addLayer(String name)
{
iLayers.push_back(SLayer(name));
iLayers.back().iVisible.resize(countViews());
for (int i = 0; i < countViews(); ++i)
iLayers.back().iVisible[i] = false;
}
//! Find layer with given name.
/*! Returns -1 if not found. */
int Page::findLayer(String name) const
{
for (int i = 0; i < countLayers(); ++i)
if (layer(i) == name)
return i;
return -1;
}
const char * const layerNames[] = {
"alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta",
"theta", "iota", "kappa", "lambda", "mu", "nu", "xi",
"omicron", "pi", "rho", "sigma", "tau", "upsilon", "phi",
"chi", "psi", "omega" };
//! Add a new layer with unique name.
void Page::addLayer()
{
for (int i = 0; i < int(sizeof(layerNames)/sizeof(const char *)); ++i) {
if (findLayer(layerNames[i]) < 0) {
addLayer(layerNames[i]);
return;
}
}
char name[20];
int i = 1;
for (;;) {
std::sprintf(name, "alpha%d", i);
if (findLayer(name) < 0) {
addLayer(name);
return;
}
i++;
}
}
//! Moves the position of a layer in the layer list.
void Page::moveLayer(int index, int newIndex)
{
assert(0 <= index && index < int(iLayers.size())
&& 0 <= newIndex && newIndex < int(iLayers.size()));
SLayer layer = iLayers[index];
iLayers.erase(iLayers.begin() + index);
iLayers.insert(iLayers.begin() + newIndex, layer);
// Layer of object needs to be decreased by one if > index
// Then increase by one if >= newIndex
// If == index, then replace by newIndex
for (ObjSeq::iterator it = iObjects.begin(); it != iObjects.end(); ++it) {
int k = it->iLayer;
if (k == index) {
k = newIndex;
} else {
if (k > index)
k -= 1;
if (k >= newIndex)
k += 1;
}
it->iLayer = k;
}
}
//! Removes an empty layer from the page.
/*! All objects are adjusted. Panics if there are objects in the
deleted layer, of if it is the only layer.
*/
void Page::removeLayer(String name)
{
int index = findLayer(name);
assert(iLayers.size() > 1 && index >= 0);
for (ObjSeq::iterator it = iObjects.begin(); it != iObjects.end(); ++it) {
int k = it->iLayer;
assert(k != index);
if (k > index)
it->iLayer = k-1;
}
iLayers.erase(iLayers.begin() + index);
}
//! Return number of objects in each layer
void Page::objectsPerLayer(std::vector &objcounts) const
{
objcounts.clear();
for (int i = 0; i < countLayers(); ++i)
objcounts.push_back(0);
for (const auto & obj : iObjects)
objcounts[obj.iLayer] += 1;
}
//! Rename a layer.
void Page::renameLayer(String oldName, String newName)
{
int l = findLayer(oldName);
if (l < 0)
return;
iLayers[l].iName = newName;
}
//! Returns a precise bounding box for the artwork on the page.
/*! This is meant to be used as the bounding box in PDF output. It is
computed by rendering all objects on the page that are visible in at
least one view, plus all objects in a layer named "BBOX" (even if
that is not visible), using a BBoxPainter. */
Rect Page::pageBBox(const Cascade *sheet) const
{
// make table with all layers to be rendered
std::vector layers;
for (int l = 0; l < countLayers(); ++l) {
bool vis = false;
if (layer(l) == "BBOX")
vis = true;
else
for (int vno = 0; !vis && vno < countViews(); ++vno)
vis = visible(vno, l);
layers.push_back(vis);
}
BBoxPainter bboxPainter(sheet);
for (int i = 0; i < count(); ++i) {
if (layers[layerOf(i)])
object(i)->draw(bboxPainter);
}
return bboxPainter.bbox();
}
//! Returns a precise bounding box for the artwork in the view.
/*! This is meant to be used as the bounding box in PDF and EPS
output. It is computed by rendering all objects in the page using a
BBoxPainter. */
Rect Page::viewBBox(const Cascade *sheet, int view) const
{
BBoxPainter bboxPainter(sheet);
for (int i = 0; i < count(); ++i) {
if (objectVisible(view, i))
object(i)->draw(bboxPainter);
}
return bboxPainter.bbox();
}
//! Snapping occurs if the layer is visible and has snapping enabled.
bool Page::objSnapsInView(int objNo, int view) const noexcept
{
int layer = layerOf(objNo);
return hasSnapping(layer) && visible(view, layer);
}
// --------------------------------------------------------------------
//! Set effect of view.
/*! Panics if \a sym is not symbolic. */
void Page::setEffect(int index, Attribute sym)
{
assert(sym.isSymbolic());
iViews[index].iEffect = sym;
}
//! Set active layer of view.
void Page::setActive(int index, String layer)
{
assert(findLayer(layer) >= 0);
iViews[index].iActive = layer;
}
//! Set visibility of layer \a layer in view \a view.
void Page::setVisible(int view, String layer, bool vis)
{
int index = findLayer(layer);
assert(index >= 0);
iLayers[index].iVisible[view] = vis;
}
//! Insert a new view at index \a i.
void Page::insertView(int i, String active)
{
iViews.insert(iViews.begin() + i, SView());
iViews[i].iActive = active;
iViews[i].iMarked = false;
for (int l = 0; l < countLayers(); ++l)
iLayers[l].iVisible.insert(iLayers[l].iVisible.begin() + i, false);
}
//! Remove the view at index \a i.
void Page::removeView(int i)
{
iViews.erase(iViews.begin() + i);
for (int l = 0; l < countLayers(); ++l)
iLayers[l].iVisible.erase(iLayers[l].iVisible.begin() + i);
}
//! Remove all views of this page.
void Page::clearViews()
{
iViews.clear();
for (LayerSeq::iterator it = iLayers.begin();
it != iLayers.end(); ++it)
it->iVisible.clear();
}
void Page::setMarkedView(int index, bool marked)
{
iViews[index].iMarked = marked;
}
int Page::countMarkedViews() const
{
int count = 0;
for (int i = 0; i < countViews(); ++i) {
if (markedView(i))
++count;
}
return (count == 0) ? 1 : count;
}
//! Return index of view with given number or name.
/*! Input numbers are one-based.
Returns -1 if no such view exists.
*/
int Page::findView(String s) const
{
if (s.empty())
return -1;
if ('0' <= s[0] && s[0] <= '9') {
int no = Lex(s).getInt();
if (no <= 0 || no > countViews())
return -1;
return no - 1;
}
for (int i = 0; i < countViews(); ++i) {
if (s == viewName(i))
return i;
}
return -1;
}
// --------------------------------------------------------------------
Page::SObject::SObject()
{
iObject = nullptr;
iLayer = 0;
iSelect = ENotSelected;
}
Page::SObject::SObject(const SObject &rhs)
: iSelect(rhs.iSelect), iLayer(rhs.iLayer)
{
if (rhs.iObject)
iObject = rhs.iObject->clone();
else
iObject = nullptr;
}
Page::SObject &Page::SObject::operator=(const SObject &rhs)
{
if (this != &rhs) {
delete iObject;
iSelect = rhs.iSelect;
iLayer = rhs.iLayer;
if (rhs.iObject)
iObject = rhs.iObject->clone();
else
iObject = nullptr;
iBBox.clear(); // invalidate
}
return *this;
}
Page::SObject::~SObject()
{
delete iObject;
}
// --------------------------------------------------------------------
//! Insert a new object at index \a i.
/*! Takes ownership of the object. */
void Page::insert(int i, TSelect select, int layer, Object *obj)
{
iObjects.insert(iObjects.begin() + i, SObject());
SObject &s = iObjects[i];
s.iSelect = select;
s.iLayer = layer;
s.iObject = obj;
}
//! Append a new object.
/*! Takes ownership of the object. */
void Page::append(TSelect select, int layer, Object *obj)
{
iObjects.push_back(SObject());
SObject &s = iObjects.back();
s.iSelect = select;
s.iLayer = layer;
s.iObject = obj;
}
//! Remove the object at index \a i.
void Page::remove(int i)
{
iObjects.erase(iObjects.begin() + i);
}
//! Replace the object at index \a i.
/*! Takes ownership of \a obj. */
void Page::replace(int i, Object *obj)
{
delete iObjects[i].iObject;
iObjects[i].iObject = obj;
invalidateBBox(i);
}
//! Return distance between object at index \a i and \a v.
/*! But may just return \a bound if the distance is larger.
This function uses the cached bounded box, and is faster than
calling Object::distance directly. */
double Page::distance(int i, const Vector &v, double bound) const
{
if (bbox(i).certainClearance(v, bound))
return bound;
return object(i)->distance(v, Matrix(), bound);
}
//! Transform the object at index \a i.
/*! Use this function instead of calling Object::setMatrix directly,
as the latter doesn't invalidate the cached bounding box. */
void Page::transform(int i, const Matrix &m)
{
invalidateBBox(i);
object(i)->setMatrix(m * object(i)->matrix());
}
//! Invalidate the bounding box at index \a i (the object is somehow changed).
void Page::invalidateBBox(int i) const
{
iObjects[i].iBBox.clear();
}
//! Return a bounding box for the object at index \a i.
/*! This is a bounding box including the control points of the object.
If you need a tight bounding box, you'll need to use the Object
directly.
The Page caches the box the first time it is computed.
Make sure you call Page::transform instead of Object::setMatrix,
as the latter would not invalidate the bounding box.
*/
Rect Page::bbox(int i) const
{
if (iObjects[i].iBBox.isEmpty())
iObjects[i].iObject->addToBBox(iObjects[i].iBBox, Matrix(), true);
return iObjects[i].iBBox;
}
//! Compute possible vertex snapping position for object at index \a i.
/*! Looks only for positions closer than \a bound.
If successful, modifies \a pos and \a bound. */
void Page::snapVtx(int i, const Vector &mouse,
Vector &pos, double &bound) const
{
if (bbox(i).certainClearance(mouse, bound))
return;
object(i)->snapVtx(mouse, Matrix(), pos, bound);
}
//! Compute possible control point snapping position for object at index \a i.
/*! Looks only for positions closer than \a bound.
If successful, modifies \a pos and \a bound. */
void Page::snapCtl(int i, const Vector &mouse,
Vector &pos, double &bound) const
{
if (bbox(i).certainClearance(mouse, bound))
return;
object(i)->snapCtl(mouse, Matrix(), pos, bound);
}
//! Compute possible boundary snapping position for object at index \a i.
/*! Looks only for positions closer than \a bound.
If successful, modifies \a pos and \a bound. */
void Page::snapBnd(int i, const Vector &mouse,
Vector &pos, double &bound) const
{
if (bbox(i).certainClearance(mouse, bound))
return;
object(i)->snapBnd(mouse, Matrix(), pos, bound);
}
//! Set attribute \a prop of object at index \a i to \a value.
/*! This method automatically invalidates the bounding box if a
ETextSize property is actually changed. */
bool Page::setAttribute(int i, Property prop, Attribute value)
{
bool changed = object(i)->setAttribute(prop, value);
if (changed && (prop == EPropTextSize || prop == EPropTransformations))
invalidateBBox(i);
return changed;
}
// --------------------------------------------------------------------
//! Return section title at \a level.
/*! Level 0 is the section, level 1 the subsection. */
String Page::section(int level) const
{
if (iUseTitle[level])
return title();
else
return iSection[level];
}
//! Set the section title at \a level.
/*! Level 0 is the section, level 1 the subsection.
If \a useTitle is \c true, then \a name is ignored, and the section
title will be copied from the page title (and further changes to the
page title are automatically reflected). */
void Page::setSection(int level, bool useTitle, String name)
{
iUseTitle[level] = useTitle;
iSection[level] = useTitle ? String() : name;
}
//! Set the title of this page.
/*! An empty title is not displayed. */
void Page::setTitle(String title)
{
iTitle = title;
iTitleObject.setText(String("\\PageTitle{") + title + "}");
}
//! Return title of this page.
String Page::title() const
{
return iTitle;
}
//! Set the notes of this page.
void Page::setNotes(String notes)
{
iNotes = notes;
}
//! Set if page is marked for printing.
void Page::setMarked(bool marked)
{
iMarked = marked;
}
//! Return Text object representing the title text.
/*! Return 0 if no title is set.
Ownership of object remains with Page.
*/
const Text *Page::titleText() const
{
if (title().empty())
return nullptr;
return &iTitleObject;
}
//! Apply styling to title text object.
void Page::applyTitleStyle(const Cascade *sheet)
{
if (title().empty())
return;
const StyleSheet::TitleStyle *ts = sheet->findTitleStyle();
if (!ts)
return;
iTitleObject.setMatrix(Matrix(ts->iPos));
iTitleObject.setSize(ts->iSize);
iTitleObject.setStroke(ts->iColor);
iTitleObject.setHorizontalAlignment(ts->iHorizontalAlignment);
iTitleObject.setVerticalAlignment(ts->iVerticalAlignment);
}
// --------------------------------------------------------------------
//! Return index of primary selection.
/*! Returns -1 if there is no primary selection. */
int Page::primarySelection() const
{
for (int i = 0; i < count(); ++i)
if (select(i) == EPrimarySelected)
return i;
return -1;
}
//! Returns true iff any object on the page is selected.
bool Page::hasSelection() const
{
for (int i = 0; i < count(); ++i)
if (select(i))
return true;
return false;
}
//! Deselect all objects.
void Page::deselectAll()
{
for (int i = 0; i < count(); ++i)
setSelect(i, ENotSelected);
}
/*! If no object is the primary selection, make the topmost secondary
selection the primary one. */
void Page::ensurePrimarySelection()
{
for (int i = 0; i < count(); ++i)
if (select(i) == EPrimarySelected)
return;
for (int i = count() - 1; i >= 0; --i) {
if (select(i) == ESecondarySelected) {
setSelect(i, EPrimarySelected);
return;
}
}
}
//! Copy whole page with bitmaps as into the stream.
void Page::saveAsIpePage(Stream &stream) const
{
BitmapFinder bmFinder;
bmFinder.scanPage(this);
stream << "\n";
int id = 1;
for (std::vector::const_iterator it = bmFinder.iBitmaps.begin();
it != bmFinder.iBitmaps.end(); ++it) {
Bitmap bm = *it;
bm.saveAsXml(stream, id);
bm.setObjNum(id);
++id;
}
saveAsXml(stream);
stream << "\n";
}
//! Copy selected objects as into the stream.
void Page::saveSelection(Stream &stream) const
{
BitmapFinder bmFinder;
for (int i = 0; i < count(); ++i) {
if (select(i))
object(i)->accept(bmFinder);
}
stream << "\n";
int id = 1;
for (std::vector::const_iterator it = bmFinder.iBitmaps.begin();
it != bmFinder.iBitmaps.end(); ++it) {
Bitmap bm = *it;
bm.saveAsXml(stream, id);
bm.setObjNum(id);
++id;
}
for (int i = 0; i < count(); ++i) {
if (select(i))
object(i)->saveAsXml(stream, String());
}
stream << "\n";
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/iperesources.cpp 0000644 0001750 0001750 00000015430 13561570220 017506 0 ustar otfried otfried // --------------------------------------------------------------------
// PDF Resources
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "iperesources.h"
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::PdfResourceBase
* \ingroup base
* \brief Base class providing access to PDF objects.
*/
PdfResourceBase::PdfResourceBase() : iPageResources(new PdfDict)
{
// nothing
}
PdfResourceBase::~PdfResourceBase()
{
// nothing
}
const PdfObj *PdfResourceBase::getDeep(const PdfDict *d, String key) const noexcept
{
if (!d)
return nullptr;
const PdfObj *obj = d->get(key, nullptr);
if (obj && obj->ref())
obj = object(obj->ref()->value());
return obj;
}
const PdfDict *PdfResourceBase::getDict(const PdfDict *d, String key) const noexcept
{
const PdfObj *obj = getDeep(d, key);
if (obj)
return obj->dict();
return nullptr;
}
const PdfDict *PdfResourceBase::resourcesOfKind(String kind) const noexcept
{
const PdfObj *obj = iPageResources->get(kind, nullptr);
if (!obj)
return nullptr;
else
return obj->dict();
}
const PdfDict *PdfResourceBase::findResource(String kind, String name) const noexcept
{
return getDict(resourcesOfKind(kind), name);
}
const PdfDict *PdfResourceBase::findResource(const PdfDict *xf, String kind,
String name) const noexcept
{
const PdfDict *res = getDict(xf, "Resources");
const PdfDict *kindd = getDict(res, kind);
return getDict(kindd, name);
}
// --------------------------------------------------------------------
/*! \class ipe::PdfFileResource
* \ingroup base
* \brief PDF resources directly from a PdfFile.
*/
PdfFileResources::PdfFileResources(const PdfFile *file) : iPdf(file)
{
// nothing
}
const PdfObj *PdfFileResources::object(int num) const noexcept
{
return iPdf->object(num);
}
// --------------------------------------------------------------------
/*! \class ipe::PdfResources
* \ingroup base
* \brief All the resources needed by the text objects in the document.
*/
PdfResources::PdfResources()
{
// nothing
}
void PdfResources::add(int num, PdfFile *file)
{
if (object(num)) // already present
return;
std::unique_ptr obj = file->take(num);
if (!obj)
return; // no such object
const PdfObj *q = obj.get();
iObjects[num] = std::move(obj);
addIndirect(q, file);
iEmbedSequence.push_back(num); // after all its dependencies!
}
void PdfResources::addIndirect(const PdfObj *q, PdfFile *file)
{
if (q->array()) {
const PdfArray *arr = q->array();
for (int i = 0; i < arr->count(); ++i)
addIndirect(arr->obj(i, nullptr), file);
} else if (q->dict()) {
const PdfDict *dict = q->dict();
for (int i = 0; i < dict->count(); ++i)
addIndirect(dict->value(i), file);
} else if (q->ref())
add(q->ref()->value(), file);
}
const PdfObj *PdfResources::object(int num) const noexcept
{
auto got = iObjects.find(num);
if (got != iObjects.end())
return got->second.get();
else
return nullptr;
}
const PdfDict *PdfResources::baseResources() const noexcept
{
return iPageResources.get();
}
//! Collect (recursively) all the given resources (of the one latex page).
//! Takes ownership of all the scanned objects.
bool PdfResources::collect(const PdfDict *resd, PdfFile *file)
{
/* A resource is a dictionary, like this:
/Font << /F8 9 0 R /F10 18 0 R >>
/ProcSet [ /PDF /Text ]
*/
for (int i = 0; i < resd->count(); ++i) {
String key = resd->key(i);
if (key == "Ipe" || key == "ProcSet")
continue;
const PdfObj *obj = resd->get(key, file);
const PdfDict *rd = obj->dict();
if (!rd) {
ipeDebug("Resource %s is not a dictionary", key.z());
return false;
}
PdfDict *d = new PdfDict;
for (int j = 0; j < rd->count(); ++j) {
if (!addToResource(d, rd->key(j), rd->value(j), file))
return false;
}
iPageResources->add(key, d);
}
return true;
}
bool PdfResources::addToResource(PdfDict *d, String key,
const PdfObj *el, PdfFile *file)
{
if (el->name())
d->add(key, new PdfName(el->name()->value()));
else if (el->number())
d->add(key, new PdfNumber(el->number()->value()));
else if (el->ref()) {
int ref = el->ref()->value();
d->add(key, new PdfRef(ref));
add(ref, file); // take all dependencies from file
} else if (el->array()) {
PdfArray *a = new PdfArray;
for (int i = 0; i < el->array()->count(); ++i) {
const PdfObj *al = el->array()->obj(i, nullptr);
if (al->name())
a->append(new PdfName(al->name()->value()));
else if (al->number())
a->append(new PdfNumber(al->number()->value()));
else {
ipeDebug("Surprising type in resource: %s", el->repr().z());
return false;
}
}
d->add(key, a);
} else if (el->dict()) {
const PdfDict *eld = el->dict();
PdfDict *d1 = new PdfDict;
for (int i = 0; i < eld->count(); ++i) {
if (!addToResource(d1, eld->key(i), eld->value(i), file))
return false;
}
d->add(key, d1);
}
return true;
}
void PdfResources::show() const noexcept
{
ipeDebug("Resources:\n%s", iPageResources->repr().z());
}
// --------------------------------------------------------------------
void PdfResources::addPageNumber(SPageNumber &pn) noexcept
{
iPageNumbers.emplace_back(std::move(pn));
}
const Text *PdfResources::pageNumber(int page, int view) const noexcept
{
auto it = std::find_if(iPageNumbers.begin(), iPageNumbers.end(),
[page, view](const SPageNumber &pn)
{ return pn.page == page && pn.view == view; } );
if (it == iPageNumbers.end())
return nullptr;
else
return it->text.get();
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipeattributes.cpp 0000644 0001750 0001750 00000035244 13561570220 017667 0 ustar otfried otfried // -*- C++ -*-
// --------------------------------------------------------------------
// Ipe object attributes
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipeattributes.h"
// --------------------------------------------------------------------
/*! \defgroup attr Ipe Attributes
\brief Attributes for Ipe objects.
Ipe objects have attributes such as color, line width, dash pattern,
etc. Most attributes can be symbolic (the need to be looked up in a
style sheet before rendering) or absolute.
The Color class represents absolute values of colors. The class
Attribute encapsulates all attributes that can be either symbolic or
absolute.
The Lua bindings for attributes are described
\ref luaobj "here".
*/
// --------------------------------------------------------------------
namespace ipe {
const char *const kind_names[] = {
"pen", "symbolsize", "arrowsize", "color",
"dashstyle", "textsize", "textstretch", "textstyle", "labelstyle",
"gridsize", "anglesize", "opacity", "tiling",
"symbol", "gradient", "effect", nullptr };
const char *const property_names[] = {
"pen", "symbolsize",
"farrow", "rarrow",
"farrowsize", "rarrowsize",
"farrowshape", "rarrowshape",
"stroke", "fill", "markshape",
"pathmode", "dashstyle",
"textsize", "textstyle", "labelstyle",
"opacity", "strokeopacity", "tiling", "gradient",
"horizontalalignment", "verticalalignment",
"linejoin", "linecap", "fillrule",
"pinned", "transformations", "transformabletext",
"minipage", "width", "decoration", nullptr };
}
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::Color
\ingroup attr
\brief An absolute RGB color.
*/
//! Construct a color.
/*! Arguments \a red, \a green, \a blue range from 0 to 1000. */
Color::Color(int red, int green, int blue)
{
iRed = Fixed::fromInternal(red);
iGreen = Fixed::fromInternal(green);
iBlue = Fixed::fromInternal(blue);
}
//! Save to stream.
void Color::save(Stream &stream) const
{
if (isGray())
stream << iRed;
else
stream << iRed << " " << iGreen << " " << iBlue;
}
//! Save to stream in long format.
/*! The RGB components are saved separately even for gray colors. */
void Color::saveRGB(Stream &stream) const
{
stream << iRed << " " << iGreen << " " << iBlue;
}
//! is it an absolute gray value?
bool Color::isGray() const
{
return (iRed == iGreen && iRed == iBlue);
}
bool Color::operator==(const Color &rhs) const
{
return (iRed == rhs.iRed) && (iGreen == rhs.iGreen) && (iBlue == rhs.iBlue);
}
// --------------------------------------------------------------------
/*! \class ipe::Effect
\ingroup attr
\brief Effect that Acrobat Reader will show on page change.
Acrobat Reader and other PDF viewers can show a special effect when
a new page of the document is shown. This class describes such an
effect.
*/
//! Construct default effect.
Effect::Effect()
{
iEffect = ENormal;
iDuration = 0;
iTransitionTime = 1;
}
//! Write part of page dictionary.
/*! Write part of page dictionary indicating effect,
including the two keys /Dur and /Trans. */
void Effect::pageDictionary(Stream &stream) const
{
if (iDuration > 0)
stream << "/Dur " << iDuration << "\n";
if (iEffect != ENormal) {
stream << "/Trans << /D " << iTransitionTime << " /S ";
switch (iEffect) {
case ESplitHI: stream << "/Split /Dm /H /M /I"; break;
case ESplitHO: stream << "/Split /Dm /H /M /O"; break;
case ESplitVI: stream << "/Split /Dm /V /M /I"; break;
case ESplitVO: stream << "/Split /Dm /V /M /O"; break;
case EBlindsH: stream << "/Blinds /Dm /H"; break;
case EBlindsV: stream << "/Blinds /Dm /V"; break;
case EBoxI: stream << "/Box /M /I"; break;
case EBoxO: stream << "/Box /M /O"; break;
case EWipeLR: stream << "/Wipe /Di 0"; break;
case EWipeBT: stream << "/Wipe /Di 90"; break;
case EWipeRL: stream << "/Wipe /Di 180"; break;
case EWipeTB: stream << "/Wipe /Di 270"; break;
case EDissolve: stream << "/Dissolve"; break;
case EGlitterLR: stream << "/Glitter /Di 0"; break;
case EGlitterTB: stream << "/Glitter /Di 270"; break;
case EGlitterD: stream << "/Glitter /Di 315"; break;
case ENormal: break; // to satisfy compiler
}
stream << " >>\n";
}
}
// --------------------------------------------------------------------
/*! \class ipe::Repository
\ingroup attr
\brief Repository of strings.
Ipe documents can use symbolic attributes, such as 'normal', 'fat',
or 'thin' for line thickness, or 'red', 'navy', 'turquoise' for
color, as well as absolute attributes such as "[3 1] 0" for a dash
pattern. To avoid storing these common strings hundreds of times,
Repository keeps a repository of strings. Inside an Object, strings
are replaced by indices into the repository.
The Repository is a singleton object. It is created the first time
it is used. You obtain access to the repository using get().
*/
// pointer to singleton object
Repository *Repository::singleton = nullptr;
//! Constructor.
Repository::Repository()
{
// put certain strings at index 0 ..
iStrings.push_back("normal");
iStrings.push_back("undefined");
iStrings.push_back("Background");
iStrings.push_back("sym-stroke");
iStrings.push_back("sym-fill");
iStrings.push_back("sym-pen");
iStrings.push_back("arrow/normal(spx)");
iStrings.push_back("opaque");
iStrings.push_back("arrow/arc(spx)");
iStrings.push_back("arrow/farc(spx)");
iStrings.push_back("arrow/ptarc(spx)");
iStrings.push_back("arrow/fptarc(spx)");
}
//! Get pointer to singleton Repository.
Repository *Repository::get()
{
if (!singleton) {
singleton = new Repository();
}
return singleton;
}
//! Return string with given index.
String Repository::toString(int index) const
{
return iStrings[index];
}
//! Return index of given string.
/*! The string is added to the repository if it doesn't exist yet. */
int Repository::toIndex(String str)
{
assert(!str.empty());
std::vector::const_iterator it =
std::find(iStrings.begin(), iStrings.end(), str);
if (it != iStrings.end())
return (it - iStrings.begin());
iStrings.push_back(str);
return iStrings.size() - 1;
}
//! Destroy repository object.
void Repository::cleanup()
{
delete singleton;
singleton = nullptr;
}
// --------------------------------------------------------------------
/*! \class ipe::Attribute
\ingroup attr
\brief An attribute of an Ipe Object.
An attribute is either an absolute value or a symbolic name that has
to be looked up in a StyleSheet.
All string values are replaced by indices into a Repository (that
applies both to symbolic names and to absolute values that are
strings). All other values are stored directly inside the
attribute, either as a Fixed or a Color.
There are five different kinds of Attribute objects:
- if isSymbolic() is true, index() returns the index into the
repository, and string() returns the symbolic name.
- if isColor() is true, color() returns an absolute RGB color.
- if isNumeric() is true, number() returns an absolute scalar value.
- if isEnum() is true, the attribute represents an enumeration value.
- otherwise, isString() is true, and index() returns the index into
the repository (for a string expressing the absolute value of the
attribute), and string() returns the string itself.
*/
/*
if (n & 0xc000.0000) == 0x0000.0000 -> color in 0x3fff.ffff
if (n & 0xc000.0000) == 0x4000.0000 -> fixed in 0x3fff.ffff
if (n & 0xe000.0000) == 0x8000.0000 -> symbolic string in 0x1fffffff
if (n & 0xe000.0000) == 0xc000.0000 -> absolute string in 0x1fffffff
if (n & 0xe000.0000) == 0xe000.0000 -> enum in 0x1fff.ffff
*/
//! Create an attribute with absolute color.
Attribute::Attribute(Color color)
{
iName = (color.iRed.internal() << 20) + (color.iGreen.internal() << 10)
+ (color.iBlue.internal());
}
//! Create an attribute with string value.
Attribute::Attribute(bool symbolic, int index)
{
iName = index | (symbolic ? ESymbolic : EAbsolute);
}
//! Create an absolute numeric attribute.
Attribute::Attribute(Fixed value)
{
iName = EFixed | value.internal();
}
//! Create an attribute with string value.
Attribute::Attribute(bool symbolic, String name)
{
int index = Repository::get()->toIndex(name);
iName = index | (symbolic ? ESymbolic : EAbsolute);
}
static const char * const enumeration_name[] =
{ "false", "true", // bool
"left", "right", "hcenter", // halign
"bottom", "baseline", "top", "vcenter", // valign
"normal", "miter", "round", "bevel", // linejoin
"normal", "butt", "round", "square", // linecap
"normal", "wind", "evenodd", // fillrule
"none", "horizontal", "vertical", "fixed", // pinned
"translations", "rigid", "affine", // transformations
"stroked", "strokedfilled", "filled", // pathmode
};
//! Return string representing the attribute.
String Attribute::string() const
{
if (isSymbolic() || isString())
return Repository::get()->toString(index());
String str;
StringStream stream(str);
if (isNumber()) {
stream << number();
} else if (isColor()) {
stream << color();
} else {
// is enumeration value
stream << enumeration_name[index()];
}
return str;
}
//! Return value of absolute numeric attribute.
Fixed Attribute::number() const
{
assert(isNumber());
return Fixed::fromInternal(iName & EFixedMask);
}
//! Return absolute color.
Color Attribute::color() const
{
assert(isColor());
Color col;
col.iRed = Fixed::fromInternal(iName >> 20);
col.iGreen = Fixed::fromInternal((iName >> 10) & 0x3ff);
col.iBlue = Fixed::fromInternal(iName & 0x3ff);
return col;
}
//! Construct a color from a string.
/*! If only a single number is given, this is a gray value, otherwise
red, green, and blue components must be given. */
Color::Color(String str)
{
Lex st(str);
st >> iRed >> iGreen;
if (st.eos())
iGreen = iBlue = iRed;
else
st >> iBlue;
}
//! Make a color attribute.
/*! If the string starts with a letter, make a symbolic attribute.
Otherwise, it's either a single gray value (0.0 to 1.0), or the
three red, green, and blue components, separated by spaces.
If it's an empty string, return \a deflt.
*/
Attribute Attribute::makeColor(String str, Attribute deflt)
{
if (str.empty())
return deflt;
else if (('a' <= str[0] && str[0] <= 'z') ||
('A' <= str[0] && str[0] <= 'Z'))
return Attribute(true, str);
else
return Attribute(Color(str));
}
//! Make a scalar attribute.
/*! If \a str is empty, simply return \a deflt.
If \a str starts with a letter, make a symbolic attribute.
Otherwise, must be a number. */
Attribute Attribute::makeScalar(String str, Attribute deflt)
{
if (str.empty()) {
return deflt;
} else if (('a' <= str[0] && str[0] <= 'z') ||
('A' <= str[0] && str[0] <= 'Z')) {
return Attribute(true, str);
} else {
return Attribute(Lex(str).getFixed());
}
}
//! Construct dash style attribute from string.
/*! Strings starting with '[' create an absolute dash style. The
empty string is equivalent to 'normal'. Any other string creates a
symbolic dash style.
*/
Attribute Attribute::makeDashStyle(String str)
{
if (str.empty())
return Attribute::NORMAL();
else if (str[0] == '[')
return Attribute(false, str);
else
return Attribute(true, str);
}
//! Construct text size attribute from string.
/*! String starting with digit creates a numeric absolute value,
string starting with letter creates symbolic text size, anything
else creates absolute (string) text size. The empty string is
treated like "normal".
*/
Attribute Attribute::makeTextSize(String str)
{
if (str.empty())
return Attribute::NORMAL();
else if ('0' <= str[0] && str[0] <= '9')
return Attribute(Lex(str).getFixed());
else if (('a' <= str[0] && str[0] <= 'z') ||
('A' <= str[0] && str[0] <= 'Z'))
return Attribute(true, str);
else
return Attribute(false, str);
}
//! Return a standard value for attribute of \a kind.
/*! The value is used if the stylesheet doesn't define a symbolic
attribute used in the document. */
Attribute Attribute::normal(Kind kind)
{
switch (kind) {
case EPen:
case EArrowSize:
case ESymbolSize:
case ETextSize:
case ETextStyle:
case EDashStyle:
default:
return Attribute::NORMAL();
case ETextStretch:
case EOpacity:
return Attribute::ONE();
case EColor:
return Attribute::BLACK();
case EGridSize:
return Attribute(Fixed(8));
case EAngleSize:
return Attribute(Fixed(45));
}
}
// --------------------------------------------------------------------
/*! \class ipe::AllAttributes
\ingroup attr
\brief Collection of all object attributes.
*/
//! Constructor sets default values.
AllAttributes::AllAttributes()
{
iStroke = iFill = Attribute::BLACK();
iPathMode = EStrokedOnly;
iDashStyle = Attribute::NORMAL();
iPen = iFArrowSize = iRArrowSize = Attribute::NORMAL();
iFArrowShape = iRArrowShape = Attribute::ARROW_NORMAL();
iFArrow = iRArrow = false;
iSymbolSize = iTextSize = iTextStyle = iLabelStyle = Attribute::NORMAL();
iTransformableText = false;
iVerticalAlignment = EAlignBaseline;
iHorizontalAlignment = EAlignLeft;
iPinned = ENoPin;
iTransformations = ETransformationsAffine;
iLineJoin = EDefaultJoin;
iLineCap = EDefaultCap;
iFillRule = EDefaultRule;
iOpacity = Attribute::OPAQUE();
iStrokeOpacity = Attribute::OPAQUE();
iTiling = Attribute::NORMAL();
iGradient = Attribute::NORMAL();
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipeiml.cpp 0000644 0001750 0001750 00000050232 13561570220 016254 0 ustar otfried otfried // --------------------------------------------------------------------
// IpeImlParser
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipeiml.h"
#include "ipeobject.h"
#include "ipegroup.h"
#include "ipepage.h"
#include "ipefactory.h"
#include "ipestyle.h"
#include "ipereference.h"
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::ImlParser
\ingroup high
\brief XML Parser for Ipe documents and style sheets.
A recursive descent parser for the XML streams.
After experimenting with various XML parsing frameworks, this turned
out to work best for Ipe.
*/
ImlParser::ImlParser(DataSource &source)
: XmlParser(source)
{
// nothing
}
//! XML contents can refer to data in PDF.
/*! If the XML stream is embedded in a PDF file, XML contents can
refer to PDF objects. A derived parser must implement this method
to access PDF data.
It is assumed that PDF object \a objNum is a stream. Its contents
(uncompressed!) is returned in a buffer.
*/
Buffer ImlParser::pdfStream(int /* objNum */)
{
return Buffer();
}
//! Read a complete document from IML stream.
/*! Returns an error code. */
int ImlParser::parseDocument(Document &doc)
{
Document::SProperties properties = doc.properties();
String tag = parseToTag();
if (tag == "?xml") {
XmlAttributes attr;
if (!parseAttributes(attr, true))
return ESyntaxError;
tag = parseToTag();
}
if (tag != "ipe")
return ESyntaxError;
XmlAttributes attr;
if (!parseAttributes(attr))
return ESyntaxError;
Lex versLex(attr["version"]);
int version;
versLex >> version;
if (version < OLDEST_FILE_FORMAT)
return EVersionTooOld;
if (version > IPELIB_VERSION)
return EVersionTooRecent;
attr.has("creator", properties.iCreator);
tag = parseToTag();
if (tag == "info") {
XmlAttributes att;
if (!parseAttributes(att))
return ESyntaxError;
properties.iTitle = att["title"];
properties.iAuthor = att["author"];
properties.iSubject = att["subject"];
properties.iKeywords = att["keywords"];
properties.iFullScreen = (att["pagemode"] == "fullscreen");
properties.iNumberPages = (att["numberpages"] == "yes");
properties.iCreated = att["created"];
properties.iModified = att["modified"];
String tex = att["tex"];
if (tex == "pdftex")
properties.iTexEngine = LatexType::Pdftex;
else if (tex == "xetex")
properties.iTexEngine = LatexType::Xetex;
else if (tex == "luatex")
properties.iTexEngine = LatexType::Luatex;
tag = parseToTag();
}
if (tag == "preamble") {
XmlAttributes att;
if (!parseAttributes(att))
return ESyntaxError;
if (!parsePCDATA("preamble", properties.iPreamble))
return ESyntaxError;
tag = parseToTag();
}
// document created by default constructor already has standard stylesheet
Cascade *cascade = doc.cascade();
while (tag == "ipestyle" || tag == "bitmap") {
if (tag == "ipestyle") {
StyleSheet *sheet = new StyleSheet();
if (!parseStyle(*sheet)) {
delete sheet;
return ESyntaxError;
}
cascade->insert(0, sheet);
} else { // tag == "bitmap"
if (!parseBitmap())
return ESyntaxError;
}
tag = parseToTag();
}
while (tag == "page") {
// read one page
Page *page = new Page;
doc.push_back(page);
if (!parsePage(*page))
return ESyntaxError;
tag = parseToTag();
}
doc.setProperties(properties);
if (tag != "/ipe")
return ESyntaxError;
return ESuccess;
}
//! Parse an Bitmap.
/*! On calling, stream must be just past \c bitmap. */
bool ImlParser::parseBitmap()
{
XmlAttributes att;
if (!parseAttributes(att))
return false;
String objNumStr;
if (att.slash() && att.has("pdfObject", objNumStr)) {
Lex lex(objNumStr);
Buffer data = pdfStream(lex.getInt());
Buffer alpha;
lex.skipWhitespace();
if (!lex.eos())
alpha = pdfStream(lex.getInt());
Bitmap bitmap(att, data, alpha);
iBitmaps.push_back(bitmap);
} else {
String bits;
if (!parsePCDATA("bitmap", bits))
return false;
Bitmap bitmap(att, bits);
iBitmaps.push_back(bitmap);
}
return true;
}
//! Parse an Page.
/*! On calling, stream must be just past \c page. */
bool ImlParser::parsePage(Page &page)
{
XmlAttributes att;
if (!parseAttributes(att))
return false;
String str;
if (att.has("title", str))
page.setTitle(str);
if (att.has("section", str))
page.setSection(0, str.empty(), str);
else
page.setSection(0, false, String());
if (att.has("subsection", str))
page.setSection(1, str.empty(), str);
else
page.setSection(1, false, String());
if (att["marked"] == "no")
page.setMarked(false);
String tag = parseToTag();
if (tag == "notes") {
XmlAttributes att;
if (!parseAttributes(att))
return false;
if (!parsePCDATA("notes", str))
return false;
page.setNotes(str);
tag = parseToTag();
}
while (tag == "layer") {
XmlAttributes att;
if (!parseAttributes(att))
return false;
page.addLayer(att["name"]);
if (att["edit"] == "no")
page.setLocked(page.countLayers() - 1, true);
tag = parseToTag();
}
// default layer: 'alpha'
if (page.countLayers() == 0)
page.addLayer("alpha");
while (tag == "view") {
XmlAttributes att;
if (!parseAttributes(att))
return false;
page.insertView(page.countViews(), att["active"]);
String str;
if (att.has("effect", str))
page.setEffect(page.countViews() - 1, Attribute(true, str));
Lex st(att["layers"]);
st.skipWhitespace();
String last;
while (!st.eos()) {
last = st.nextToken();
page.setVisible(page.countViews() - 1, last, true);
st.skipWhitespace();
}
if (!att.has("active", str)) {
// if no layer visible must have active attribute
if (last.empty())
return false;
page.setActive(page.countViews() - 1, last);
}
if (att["marked"] == "yes")
page.setMarkedView(page.countViews() - 1, true);
if (att.has("name", str))
page.setViewName(page.countViews() - 1, str);
tag = parseToTag();
}
// default view: include all layers
if (page.countViews() == 0) {
int al = 0;
while (al < page.countLayers() && page.isLocked(al))
++al;
if (al == page.countLayers())
return false; // no unlocked layer
// need to synthesize a view
page.insertView(0, page.layer(al));
for (int i = 0; i < page.countLayers(); ++i)
page.setVisible(0, page.layer(i), true);
}
int currentLayer = 0;
String layerName;
for (;;) {
if (tag == "/page") {
return true;
}
if (tag.empty())
return false;
Object *obj = parseObject(tag, &page, ¤tLayer);
if (!obj)
return false;
page.insert(page.count(), ENotSelected, currentLayer, obj);
tag = parseToTag();
}
}
//! parse an \c element (used on clipboard).
Page *ImlParser::parsePageSelection()
{
String tag = parseToTag();
if (tag != "ipepage")
return nullptr;
XmlAttributes attr;
if (!parseAttributes(attr))
return nullptr;
tag = parseToTag();
while (tag == "bitmap") {
if (!parseBitmap())
return nullptr;
tag = parseToTag();
}
if (tag != "page")
return nullptr;
std::unique_ptr page(new Page);
if (!parsePage(*page))
return nullptr;
tag = parseToTag();
if (tag != "/ipepage")
return nullptr;
return page.release();
}
#if 0
//! parse an \c element.
/*! An PgObjectSeq is used to own the objects, but selection mode
and layer are not set. */
bool ImlParser::parseSelection(PageObjectSeq &seq)
{
String tag = parseToTag();
if (tag != "ipeselection")
return false;
XmlAttributes attr;
if (!parseAttributes(attr))
return nullptr;
tag = parseToTag();
while (tag == "bitmap") {
if (!parseBitmap())
return false;
tag = parseToTag();
}
for (;;) {
if (tag == "/ipeselection")
return true;
Object *obj = parseObject(tag);
if (!obj)
return false;
seq.push_back(PageObject(ENotSelected, 0, obj));
tag = parseToTag();
}
}
#endif
//! parse an Object.
/*! On calling, stream must be just past the tag. */
Object *ImlParser::parseObject(String tag, Page *page,
int *currentLayer)
{
if (tag[0] == '/')
return nullptr;
XmlAttributes attr;
if (!parseAttributes(attr))
return nullptr;
String layer;
if (page && currentLayer && attr.has("layer", layer)) {
for (int i = 0; i < page->countLayers(); ++i) {
if (page->layer(i) == layer) {
*currentLayer = i;
break;
}
}
}
if (tag == "group") {
Group group(attr);
for (;;) {
String tag = parseToTag();
if (tag == "/group") {
return new Group(group);
}
Object *obj = parseObject(tag);
if (!obj)
return nullptr;
group.push_back(obj);
}
}
String pcdata;
if (!attr.slash() && !parsePCDATA(tag, pcdata))
return nullptr;
String bitmapId;
if (tag == "image" && attr.has("bitmap", bitmapId)) {
int objNum = Lex(bitmapId).getInt();
Bitmap bitmap;
for (std::vector::const_iterator it = iBitmaps.begin();
it != iBitmaps.end(); ++it) {
if (it->objNum() == objNum) {
bitmap = *it;
break;
}
}
assert(!bitmap.isNull());
return ObjectFactory::createImage(tag, attr, bitmap);
} else
return ObjectFactory::createObject(tag, attr, pcdata);
}
static inline bool symbolName(String s)
{
return (!s.empty() && (('a' <= s[0] && s[0] <= 'z') ||
('A' <= s[0] && s[0] <= 'Z')));
}
//! Parse a style sheet.
/*! On calling, stream must be just past the style tag. */
bool ImlParser::parseStyle(StyleSheet &sheet)
{
XmlAttributes att;
if (!parseAttributes(att))
return false;
String name;
if (att.has("name", name))
sheet.setName(name);
if (att.slash())
return true;
String tag = parseToTag();
while (tag != "/ipestyle") {
if (tag == "bitmap") {
if (!parseBitmap())
return false;
} else if (tag == "symbol") {
if (!parseAttributes(att))
return false;
String tag1 = parseToTag();
Object *obj = parseObject(tag1);
if (!obj)
return false;
Symbol symbol(obj);
String name = att["name"];
if (!symbolName(name))
return false;
if (att["transformations"] == "rigid")
symbol.iTransformations = ETransformationsRigidMotions;
else if (att["transformations"] == "translations")
symbol.iTransformations = ETransformationsTranslations;
if (att["xform"] == "yes") {
uint32_t flags = Reference::flagsFromName(name);
if ((flags & (Reference::EHasStroke|
Reference::EHasFill|
Reference::EHasPen|
Reference::EHasSize)) == 0) {
symbol.iXForm = true;
symbol.iTransformations = ETransformationsTranslations;
}
}
Lex snaplex(att["snap"]);
while (!snaplex.eos()) {
Vector pos;
snaplex >> pos.x >> pos.y;
snaplex.skipWhitespace();
symbol.iSnap.push_back(pos);
}
sheet.addSymbol(Attribute(true, name), symbol);
if (parseToTag() != "/symbol")
return false;
} else if (tag == "layout") {
if (!parseAttributes(att) || !att.slash())
return false;
Layout layout;
Lex lex1(att["paper"]);
lex1 >> layout.iPaperSize.x >> layout.iPaperSize.y;
Lex lex2(att["origin"]);
lex2 >> layout.iOrigin.x >> layout.iOrigin.y;
Lex lex3(att["frame"]);
lex3 >> layout.iFrameSize.x >> layout.iFrameSize.y;
layout.iParagraphSkip = Lex(att["skip"]).getDouble();
layout.iCrop = !(att["crop"] == "no");
sheet.setLayout(layout);
} else if (tag == "textpad") {
if (!parseAttributes(att) || !att.slash())
return false;
TextPadding pad;
pad.iLeft = Lex(att["left"]).getDouble();
pad.iRight = Lex(att["right"]).getDouble();
pad.iTop = Lex(att["top"]).getDouble();
pad.iBottom = Lex(att["bottom"]).getDouble();
sheet.setTextPadding(pad);
} else if (tag == "titlestyle") {
if (!parseAttributes(att) || !att.slash())
return false;
StyleSheet::TitleStyle ts;
ts.iDefined = true;
Lex lex1(att["pos"]);
lex1 >> ts.iPos.x >> ts.iPos.y;
ts.iSize = Attribute::makeScalar(att["size"], Attribute::NORMAL());
ts.iColor = Attribute::makeColor(att["color"], Attribute::BLACK());
ts.iHorizontalAlignment = Text::makeHAlign(att["halign"], EAlignLeft);
ts.iVerticalAlignment = Text::makeVAlign(att["valign"], EAlignBaseline);
sheet.setTitleStyle(ts);
} else if (tag == "pagenumberstyle") {
if (!parseAttributes(att))
return false;
StyleSheet::PageNumberStyle pns;
pns.iDefined = true;
Lex lex1(att["pos"]);
lex1 >> pns.iPos.x >> pns.iPos.y;
pns.iSize = Attribute::makeTextSize(att["size"]);
pns.iColor = Attribute::makeColor(att["color"], Attribute::BLACK());
pns.iVerticalAlignment = Text::makeVAlign(att["valign"], EAlignBaseline);
pns.iHorizontalAlignment = Text::makeHAlign(att["halign"], EAlignLeft);
if (!att.slash() && !parsePCDATA(tag, pns.iText))
return false;
sheet.setPageNumberStyle(pns);
} else if (tag == "preamble") {
if (!parseAttributes(att))
return false;
String pcdata;
if (!att.slash() && !parsePCDATA(tag, pcdata))
return false;
sheet.setPreamble(pcdata);
} else if (tag == "pathstyle") {
if (!parseAttributes(att) || !att.slash())
return false;
String str;
if (att.has("cap", str))
sheet.setLineCap(TLineCap(Lex(str).getInt() + 1));
if (att.has("join", str))
sheet.setLineJoin(TLineJoin(Lex(str).getInt() + 1));
if (att.has("fillrule", str)) {
if (str == "wind")
sheet.setFillRule(EWindRule);
else if (str == "eofill")
sheet.setFillRule(EEvenOddRule);
}
} else if (tag == "color") {
if (!parseAttributes(att) || !att.slash())
return false;
String name = att["name"];
Attribute col =
Attribute::makeColor(att["value"], Attribute::NORMAL());
if (!symbolName(name) || !col.isColor())
return false;
sheet.add(EColor, Attribute(true, name), col);
} else if (tag == "dashstyle") {
if (!parseAttributes(att) || !att.slash())
return false;
String name = att["name"];
Attribute dash = Attribute::makeDashStyle(att["value"]);
if (!symbolName(name) || dash.isSymbolic())
return false;
sheet.add(EDashStyle, Attribute(true, name), dash);
} else if (tag == "textsize") {
if (!parseAttributes(att) || !att.slash())
return false;
String name = att["name"];
Attribute value = Attribute::makeTextSize(att["value"]);
if (!symbolName(name) || value.isSymbolic())
return false;
sheet.add(ETextSize, Attribute(true, name), value);
} else if (tag == "textstretch") {
if (!parseAttributes(att) || !att.slash())
return false;
String name = att["name"];
Attribute value =
Attribute::makeScalar(att["value"], Attribute::NORMAL());
if (!symbolName(name) || value.isSymbolic())
return false;
sheet.add(ETextStretch, Attribute(true, name), value);
} else if (tag == "gradient") {
if (!parseAttributes(att) || att.slash())
return false;
String name = att["name"];
if (!symbolName(name))
return false;
Gradient s;
s.iType = (att["type"] == "radial") ?
Gradient::ERadial : Gradient::EAxial;
Lex lex(att["coords"]);
if (s.iType == Gradient::ERadial)
lex >> s.iV[0].x >> s.iV[0].y >> s.iRadius[0]
>> s.iV[1].x >> s.iV[1].y >> s.iRadius[1];
else
lex >> s.iV[0].x >> s.iV[0].y
>> s.iV[1].x >> s.iV[1].y;
String str;
s.iExtend = (att.has("extend",str) && str == "yes");
if (att.has("matrix", str))
s.iMatrix = Matrix(str);
tag = parseToTag();
while (tag == "stop") {
if (!parseAttributes(att) || !att.slash())
return false;
Gradient::Stop st;
st.color = Color(att["color"]);
st.offset = Lex(att["offset"]).getDouble();
s.iStops.push_back(st);
tag = parseToTag();
}
if (s.iStops.size() < 2)
return false;
if (s.iStops.front().offset != 0.0) {
s.iStops.insert(s.iStops.begin(), s.iStops.front());
s.iStops.front().offset = 0.0;
}
if (s.iStops.back().offset != 1.0) {
s.iStops.push_back(s.iStops.back());
s.iStops.back().offset = 1.0;
}
if (s.iStops.front().offset < 0.0 || s.iStops.front().offset > 1.0)
return false;
for (int i = 1; i < size(s.iStops); ++i) {
if (s.iStops[i].offset < s.iStops[i-1].offset)
return false;
}
if (tag != "/gradient")
return false;
sheet.addGradient(Attribute(true, name), s);
} else if (tag == "tiling") {
if (!parseAttributes(att) || !att.slash())
return false;
String name = att["name"];
if (!symbolName(name))
return false;
Tiling s;
s.iAngle = Angle::Degrees(Lex(att["angle"]).getDouble());
s.iStep = Lex(att["step"]).getDouble();
s.iWidth = Lex(att["width"]).getDouble();
sheet.addTiling(Attribute(true, name), s);
} else if (tag == "effect") {
if (!parseAttributes(att) || !att.slash())
return false;
String name = att["name"];
if (!symbolName(name))
return false;
Effect s;
String str;
if (att.has("duration", str))
s.iDuration = Lex(str).getInt();
if (att.has("transition", str))
s.iTransitionTime = Lex(str).getInt();
if (att.has("effect", str))
s.iEffect = Effect::TEffect(Lex(str).getInt());
sheet.addEffect(Attribute(true, name), s);
} else if (tag == "textstyle") {
if (!parseAttributes(att) || !att.slash())
return false;
String name = att["name"];
if (!symbolName(name))
return false;
String value = att["begin"];
value += '\0';
value += att["end"];
Kind k = (att["type"] == "label") ? ELabelStyle : ETextStyle;
sheet.add(k, Attribute(true, name), Attribute(false, value));
} else {
Kind kind;
if (tag == "pen")
kind = EPen;
else if (tag == "symbolsize")
kind = ESymbolSize;
else if (tag == "arrowsize")
kind = EArrowSize;
else if (tag == "gridsize")
kind = EGridSize;
else if (tag == "anglesize")
kind = EAngleSize;
else if (tag == "opacity")
kind = EOpacity;
else
return false; // error
if (!parseAttributes(att) || !att.slash())
return false;
String name = att["name"];
Attribute value = Attribute::makeScalar(att["value"],
Attribute::NORMAL());
if (name.empty() || value.isSymbolic())
return false;
if (kind == EGridSize &&
(!value.isNumber() || !value.number().isInteger()))
return false; // refuse non-integer gridsize
sheet.add(kind, Attribute(true, name), value);
}
tag = parseToTag();
}
return true;
}
//! parse a complete style sheet.
/*! On calling, stream must be before the 'ipestyle' tag.
A tag is allowed.
*/
StyleSheet *ImlParser::parseStyleSheet()
{
String tag = parseToTag();
if (tag == "?xml") {
XmlAttributes attr;
if (!parseAttributes(attr, true))
return nullptr;
tag = parseToTag();
}
if (tag != "ipestyle")
return nullptr;
StyleSheet *sheet = new StyleSheet();
if (parseStyle(*sheet))
return sheet;
delete sheet;
return nullptr;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipepainter.cpp 0000644 0001750 0001750 00000040365 13561570220 017143 0 ustar otfried otfried // -*- C++ -*-
// --------------------------------------------------------------------
// Ipe drawing interface
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipepainter.h"
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::Painter
* \ingroup base
* \brief Interface for drawing.
Painter-derived classes are used for drawing to the screen and for
generating PDF and Postscript output.
The Painter maintains a stack of graphics states, which includes
stroke and fill color, line width, dash style, miter limit, line cap
and line join. It also maintains a separate stack of transformation
matrices. The Painter class takes care of maintaining the stacks,
and setting of the attributes in the current graphics state.
Setting an attribute with a symbolic value is resolved immediately
using the stylesheet Cascade attached to the Painter, so calling the
stroke() or fill() methods of Painter will return the current
absolute color.
It's okay to set symbolic attributes that the stylesheet does not
define - they are set to a default absolute value (black, solid,
etc.).
The painter is either in "general" or in "path construction" mode.
The newPath() member starts path construction mode. In this mode,
only the path construction operators (moveTo, lineTo, curveTo, rect,
drawArc, closePath), the transformation operators (transform,
untransform, translate), and the matrix stack operators (pushMatrix,
popMatrix) are admissible. The path is drawn using drawPath, this
ends path construction mode. Path construction operators cannot be
used in general mode.
The graphics state for a path must be set before starting path
construction mode, that is, before calling newPath().
Derived classes need to implement the doXXX functions for drawing
paths, images, and texts. The transformation matrix has already been
applied to the coordinates passed to the doXXX functions.
*/
//! Constructor takes a (cascaded) style sheet, which is not owned.
/*! The initial graphics state contains all default attributes. */
Painter::Painter(const Cascade *style)
{
iCascade = style;
State state;
state.iStroke = Color(0,0,0);
state.iFill = Color(1000, 1000, 1000);
state.iPen = iCascade->find(EPen, Attribute::NORMAL()).number();
state.iDashStyle = "[]0"; // solid
state.iLineCap = style->lineCap();
state.iLineJoin = style->lineJoin();
state.iFillRule = style->fillRule();
state.iSymStroke = Color(0, 0, 0);
state.iSymFill = Color(1000, 1000, 1000);
state.iSymPen = Fixed(1);
state.iOpacity = Fixed(1);
state.iStrokeOpacity = Fixed(1);
state.iTiling = Attribute::NORMAL();
state.iGradient = Attribute::NORMAL();
iState.push_back(state);
iMatrix.push_back(Matrix()); // identity
iInPath = 0;
}
//! Virtual destructor.
Painter::~Painter()
{
// nothing
}
//! Concatenate a matrix to current transformation matrix.
void Painter::transform(const Matrix &m)
{
iMatrix.back() = matrix() * m;
}
//! Reset transformation to original one, but with different origin/direction.
/*! This changes the current transformation matrix to the one set
before the first push operation, but maintaining the current origin.
Only the operations allowed in \a allowed are applied.
*/
void Painter::untransform(TTransformations trans)
{
if (trans == ETransformationsAffine)
return;
Matrix m = matrix();
Vector org = m.translation();
Vector dx = Vector(m.a[0], m.a[1]);
// Vector dy = Vector(m.a[2], m.a[3]);
Linear m1(iMatrix.front().linear());
if (trans == ETransformationsRigidMotions) {
// compute what direction is transformed to dx by original matrix
Angle alpha = (m1.inverse() * dx).angle();
// ensure that (1,0) is rotated into this orientation
m1 = m1 * Linear(alpha);
}
iMatrix.back() = Matrix(m1, org);
}
//! Concatenate a translation to current transformation matrix.
void Painter::translate(const Vector &v)
{
Matrix m;
m.a[4] = v.x;
m.a[5] = v.y;
iMatrix.back() = matrix() * m;
}
//! Enter path construction mode.
void Painter::newPath()
{
assert(!iInPath);
iInPath = iState.size(); // save current nesting level
doNewPath();
}
//! Start a new subpath.
void Painter::moveTo(const Vector &v)
{
assert(iInPath > 0);
doMoveTo(matrix() * v);
}
//! Add line segment to current subpath.
void Painter::lineTo(const Vector &v)
{
assert(iInPath > 0);
doLineTo(matrix() * v);
}
//! Add a Bezier segment to current subpath.
void Painter::curveTo(const Vector &v1, const Vector &v2, const Vector &v3)
{
assert(iInPath > 0);
doCurveTo(matrix() * v1, matrix() * v2, matrix() * v3);
}
//! Add an elliptic arc to current path.
/*! Assumes the current point is \a arc.beginp(). */
void Painter::drawArc(const Arc &arc)
{
assert(iInPath > 0);
doDrawArc(arc);
}
//! Add a rectangle subpath to the path.
/*! This is implemented in terms of moveTo() and lineTo(). */
void Painter::rect(const Rect &re)
{
moveTo(re.bottomLeft());
lineTo(re.bottomRight());
lineTo(re.topRight());
lineTo(re.topLeft());
closePath();
}
//! Close the current subpath.
void Painter::closePath()
{
assert(iInPath > 0);
doClosePath();
}
//! Save current graphics state.
/*! Cannot be called in path construction mode. */
void Painter::push()
{
assert(!iInPath);
State state = iState.back();
iState.push_back(state);
doPush();
}
//! Restore previous graphics state.
/*! Cannot be called in path construction mode. */
void Painter::pop()
{
assert(!iInPath);
iState.pop_back();
doPop();
}
//! Save current transformation matrix.
void Painter::pushMatrix()
{
iMatrix.push_back(matrix());
}
//! Restore previous transformation matrix.
void Painter::popMatrix()
{
iMatrix.pop_back();
}
//! Fill and/or stroke a path.
/*! As in PDF, a "path" can consist of several subpaths. Whether it
is filled or stroked depends on \a mode. */
void Painter::drawPath(TPathMode mode)
{
assert(iInPath > 0);
doDrawPath(mode);
iInPath = 0;
}
//! Render a bitmap.
/*! Assumes the transformation matrix has been set up to map the unit
square to the image area on the paper.
*/
void Painter::drawBitmap(Bitmap bitmap)
{
assert(!iInPath);
doDrawBitmap(bitmap);
}
//! Render a text object.
/*! Stroke color is already set, and the origin is the lower-left
corner of the text box (not the reference point!). */
void Painter::drawText(const Text *text)
{
assert(!iInPath);
doDrawText(text);
}
//! Render a symbol.
/*! The current coordinate system is already the symbol coordinate
system. If the symbol is parameterized, then sym-stroke, sym-fill,
and sym-pen are already set. */
void Painter::drawSymbol(Attribute symbol)
{
assert(!iInPath);
doDrawSymbol(symbol);
}
//! Add current path as clip path.
void Painter::addClipPath()
{
assert(iInPath > 0);
doAddClipPath();
iInPath = 0;
}
// --------------------------------------------------------------------
//! Set stroke color, resolving symbolic color and "sym-x" colors
void Painter::setStroke(Attribute color)
{
assert(!iInPath);
if (color == Attribute::SYM_STROKE())
iState.back().iStroke = iState.back().iSymStroke;
else if (color == Attribute::SYM_FILL())
iState.back().iStroke = iState.back().iSymFill;
else
iState.back().iStroke = iCascade->find(EColor, color).color();
}
//! Set fill color, resolving symbolic color.
void Painter::setFill(Attribute color)
{
assert(!iInPath);
if (color == Attribute::SYM_STROKE())
iState.back().iFill = iState.back().iSymStroke;
else if (color == Attribute::SYM_FILL())
iState.back().iFill = iState.back().iSymFill;
else
iState.back().iFill = iCascade->find(EColor, color).color();
}
//! Set pen, resolving symbolic value.
void Painter::setPen(Attribute pen)
{
assert(!iInPath);
if (pen == Attribute::SYM_PEN())
iState.back().iPen = iState.back().iSymPen;
else
iState.back().iPen = iCascade->find(EPen, pen).number();
}
//! Set dash style, resolving symbolic value.
void Painter::setDashStyle(Attribute dash)
{
assert(!iInPath);
iState.back().iDashStyle = iCascade->find(EDashStyle, dash).string();
}
//! Return dashstyle as a double sequence.
void Painter::dashStyle(std::vector &dashes, double &offset) const
{
dashes.clear();
offset = 0.0;
String s = dashStyle();
int i = s.find("[");
int j = s.find("]");
if (i < 0 || j < 0)
return;
Lex lex(s.substr(i+1, j - i - 1));
while (!lex.eos())
dashes.push_back(lex.getDouble());
offset = Lex(s.substr(j+1)).getDouble();
}
//! Set line cap.
/*! If \a cap is EDefaultCap, the current setting remains unchanged. */
void Painter::setLineCap(TLineCap cap)
{
assert(!iInPath);
if (cap != EDefaultCap)
iState.back().iLineCap = cap;
}
//! Set line join.
/*! If \a join is EDefaultJoin, the current setting remains unchanged. */
void Painter::setLineJoin(TLineJoin join)
{
assert(!iInPath);
if (join != EDefaultJoin)
iState.back().iLineJoin = join;
}
//! Set fill rule (wind or even-odd).
/*! If the rule is EDefaultRule, the current setting remains unchanged. */
void Painter::setFillRule(TFillRule rule)
{
assert(!iInPath);
if (rule != EDefaultRule)
iState.back().iFillRule = rule;
}
//! Set opacity.
void Painter::setOpacity(Attribute opaq)
{
assert(!iInPath);
iState.back().iOpacity = iCascade->find(EOpacity, opaq).number();
}
//! Set stroke opacity.
void Painter::setStrokeOpacity(Attribute opaq)
{
assert(!iInPath);
iState.back().iStrokeOpacity = iCascade->find(EOpacity, opaq).number();
}
//! Set tiling pattern.
/*! If \a tiling is not \c normal, resets the gradient pattern. */
void Painter::setTiling(Attribute tiling)
{
assert(!iInPath);
iState.back().iTiling = tiling;
if (!tiling.isNormal())
iState.back().iGradient = Attribute::NORMAL();
}
//! Set gradient fill.
/*! If \a grad is not \c normal, resets the tiling pattern. */
void Painter::setGradient(Attribute grad)
{
assert(!iInPath);
iState.back().iGradient = grad;
if (!grad.isNormal())
iState.back().iTiling = Attribute::NORMAL();
}
//! Set symbol stroke color, resolving symbolic color.
void Painter::setSymStroke(Attribute color)
{
assert(!iInPath);
if (color == Attribute::SYM_STROKE())
iState.back().iSymStroke = (++iState.rbegin())->iSymStroke;
else if (color == Attribute::SYM_FILL())
iState.back().iSymStroke = (++iState.rbegin())->iSymFill;
else
iState.back().iSymStroke = iCascade->find(EColor, color).color();
}
//! Set symbol fill color, resolving symbolic color.
void Painter::setSymFill(Attribute color)
{
assert(!iInPath);
if (color == Attribute::SYM_STROKE())
iState.back().iSymFill = (++iState.rbegin())->iSymStroke;
else if (color == Attribute::SYM_FILL())
iState.back().iSymFill = (++iState.rbegin())->iSymFill;
else
iState.back().iSymFill = iCascade->find(EColor, color).color();
}
//! Set symbol pen, resolving symbolic pen.
void Painter::setSymPen(Attribute pen)
{
assert(!iInPath);
if (pen == Attribute::SYM_PEN())
iState.back().iSymPen = (++iState.rbegin())->iSymPen;
else
iState.back().iSymPen = iCascade->find(EPen, pen).number();
}
//! Set full graphics state at once.
void Painter::setState(const State &state)
{
iState.back() = state;
}
// --------------------------------------------------------------------
// Coordinate for bezier approximation for quarter circle.
const double BETA = 0.55228474983079334;
const double PI15 = IpePi + IpeHalfPi;
//! Draw an arc of the unit circle of length \a alpha.
/*! PDF does not have an "arc" or "circle" primitive, so to draw an
arc, circle, or ellipse, Ipe has to translate it into a sequence of
Bezier curves.
The approximation is based on the following: The unit circle arc
from (1,0) to (cos a, sin a) be approximated by a Bezier spline with
control points (1, 0), (1, beta) and their mirror images along the
line with slope a/2, where
beta = 4.0 * (1.0 - cos(a/2)) / (3 * sin(a/2))
Ipe draws circles by drawing four Bezier curves for the quadrants,
and arcs by patching together quarter circle approximations with a
piece computed from the formula above.
\a alpha is normalized to [0, 2 pi], and applied starting from the
point (1,0).
The function generates a sequence of Bezier splines as calls to
curveTo. It is assumed that the caller has already executed a
moveTo to the beginning of the arc at (1,0).
This function may modify the transformation matrix.
*/
void Painter::drawArcAsBezier(double alpha)
{
// Vector p0(1.0, 0.0);
Vector p1(1.0, BETA);
Vector p2(BETA, 1.0);
Vector p3(0.0, 1.0);
Vector q1(-BETA, 1.0);
Vector q2(-1.0, BETA);
Vector q3(-1.0, 0.0);
double begAngle = 0.0;
if (alpha > IpeHalfPi) {
curveTo(p1, p2, p3);
begAngle = IpeHalfPi;
}
if (alpha > IpePi) {
curveTo(q1, q2, q3);
begAngle = IpePi;
}
if (alpha > PI15) {
curveTo(-p1, -p2, -p3);
begAngle = PI15;
}
if (alpha >= IpeTwoPi) {
curveTo(-q1, -q2, -q3);
} else {
alpha -= begAngle;
double alpha2 = alpha / 2.0;
double divi = 3.0 * sin(alpha2);
if (divi == 0.0)
return; // alpha2 is close to zero
double beta = 4.0 * (1.0 - cos(alpha2)) / divi;
Linear m = Linear(Angle(begAngle));
Vector pp1(1.0, beta);
Vector pp2 = Linear(Angle(alpha)) * Vector(1.0, -beta);
Vector pp3 = Vector(Angle(alpha));
curveTo(m * pp1, m * pp2, m * pp3);
}
}
// --------------------------------------------------------------------
//! Perform graphics state push on output medium.
void Painter::doPush()
{
// nothing
}
//! Perform graphics state pop on output medium.
void Painter::doPop()
{
// nothing
}
//! Perform new path operator.
void Painter::doNewPath()
{
// nothing
}
//! Perform moveto operator.
/*! The transformation matrix has already been applied. */
void Painter::doMoveTo(const Vector &)
{
// nothing
}
//! Perform lineto operator.
/*! The transformation matrix has already been applied. */
void Painter::doLineTo(const Vector &)
{
// nothing
}
//! Perform curveto operator.
/*! The transformation matrix has already been applied. */
void Painter::doCurveTo(const Vector &, const Vector &, const Vector &)
{
// nothing
}
//! Draw an elliptic arc.
/*! The default implementations calls drawArcAsBezier(). The
transformation matrix has not yet been applied to \a arc. */
void Painter::doDrawArc(const Arc &arc)
{
pushMatrix();
transform(arc.iM);
if (arc.isEllipse()) {
moveTo(Vector(1,0));
drawArcAsBezier(IpeTwoPi);
} else {
transform(Linear(arc.iAlpha));
double alpha = Angle(arc.iBeta - arc.iAlpha).normalize(0.0);
drawArcAsBezier(alpha);
}
popMatrix();
}
//! Perform closepath operator.
void Painter::doClosePath()
{
// nothing
}
//! Actually draw the path.
void Painter::doDrawPath(TPathMode)
{
// nothing
}
//! Draw a bitmap.
void Painter::doDrawBitmap(Bitmap)
{
// nothing
}
//! Draw a text object.
void Painter::doDrawText(const Text *)
{
// nothing
}
//! Draw a symbol.
/*! The default implementation calls the draw method of the
object. Only PDF drawing overrides this to reuse a PDF XForm. */
void Painter::doDrawSymbol(Attribute symbol)
{
const Symbol *sym = cascade()->findSymbol(symbol);
if (sym)
sym->iObject->draw(*this);
}
//! Add a clip path
void Painter::doAddClipPath()
{
// nothing
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipebitmap_unix.cpp 0000644 0001750 0001750 00000020200 13561570220 020002 0 ustar otfried otfried // ipebitmap_unix.cpp
// Code dependent on libjpeg and libpng
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipebitmap.h"
#include "ipeutils.h"
#include
#ifdef __APPLE__
#include
#else
#include
#include
#endif
using namespace ipe;
// --------------------------------------------------------------------
#ifdef __APPLE__
bool dctDecode(Buffer dctData, Buffer pixelData)
{
CGDataProviderRef source =
CGDataProviderCreateWithData(nullptr, dctData.data(), dctData.size(), nullptr);
CGImageRef bitmap =
CGImageCreateWithJPEGDataProvider(source, nullptr, false, kCGRenderingIntentDefault);
if (CGImageGetBitsPerComponent(bitmap) != 8)
return false;
int w = CGImageGetWidth(bitmap);
int h = CGImageGetHeight(bitmap);
int bits = CGImageGetBitsPerPixel(bitmap);
int stride = CGImageGetBytesPerRow(bitmap);
if (bits != 8 && bits != 24 && bits != 32)
return false;
int bytes = bits / 8;
CGBitmapInfo info = CGImageGetBitmapInfo(bitmap);
// Do we need to check for alpha channel, float pixel values, and byte order?
ipeDebug("dctDecode: %d x %d x %d, stride %d, info %x", w, h, bytes, stride, info);
CFDataRef pixels = CGDataProviderCopyData(CGImageGetDataProvider(bitmap));
const uint8_t *inRow = CFDataGetBytePtr(pixels);
uint32_t *q = (uint32_t *) pixelData.data();
if (bits != 8) {
for (int y = 0; y < h; ++y) {
const uint8_t *p = inRow;
for (int x = 0; x < w; ++x) {
*q++ = 0xff000000 | (p[0] << 16) | (p[1] << 8) | p[2];
p += bytes;
}
inRow += stride;
}
} else {
for (int y = 0; y < h; ++y) {
const uint8_t *p = inRow;
for (int x = 0; x < w; ++x)
*q++ = 0xff000000 | (*p << 16) | (*p << 8) | *p;
inRow += stride;
}
}
CFRelease(pixels);
CGImageRelease(bitmap);
CGDataProviderRelease(source);
return true;
}
#else
// Decode jpeg image using libjpeg API with error handling
// Code contributed by Michael Thon, 2015.
// The following is error-handling code for decompressing jpeg using the
// standard libjpeg API. Taken from the example.c and stackoverflow.
struct jpegErrorManager {
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
};
static char jpegLastErrorMsg[JMSG_LENGTH_MAX];
static void jpegErrorExit (j_common_ptr cinfo)
{
jpegErrorManager *myerr = (jpegErrorManager*) cinfo->err;
(*(cinfo->err->format_message)) (cinfo, jpegLastErrorMsg);
longjmp(myerr->setjmp_buffer, 1);
}
bool dctDecode(Buffer dctData, Buffer pixelData)
{
struct jpeg_decompress_struct cinfo;
// Error handling:
struct jpegErrorManager jerr;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = jpegErrorExit;
if (setjmp(jerr.setjmp_buffer)) {
ipeDebug("jpeg decompression failed: %s", jpegLastErrorMsg);
jpeg_destroy_decompress(&cinfo);
return false;
}
// Decompression:
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, (unsigned char *) dctData.data(), dctData.size());
jpeg_read_header(&cinfo, 1);
cinfo.out_color_space = JCS_RGB;
jpeg_start_decompress(&cinfo);
uint32_t *p = (uint32_t *) pixelData.data();
Buffer row(cinfo.output_width * cinfo.output_components);
uint8_t *buffer[1];
uint8_t *fin = (uint8_t *) row.data() + row.size();
while (cinfo.output_scanline < cinfo.output_height) {
buffer[0] = (uint8_t *) row.data();
jpeg_read_scanlines(&cinfo, buffer, 1);
uint32_t pixel;
uint8_t *q = (uint8_t *) row.data();
while (q < fin) {
pixel = 0xff000000 | (*q++ << 16);
pixel |= (*q++ << 8);
*p++ = pixel | *q++;
}
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return true;
}
#endif
// --------------------------------------------------------------------
//! Read PNG image from file.
/*! Returns the image as a Bitmap.
It will be compressed if \a deflate is set.
Sets \a dotsPerInch if the image file contains a resolution,
otherwise sets it to (0,0).
If reading the file fails, returns a null Bitmap,
and sets the error message \a errmsg.
*/
Bitmap Bitmap::readPNG(const char *fname, Vector &dotsPerInch, const char * &errmsg)
{
FILE *fp = Platform::fopen(fname, "rb");
if (!fp) {
errmsg = "Error opening file";
return Bitmap();
}
static const char pngerr[] = "PNG library error";
uint8_t header[8];
if (fread(header, 1, 8, fp) != 8 ||
png_sig_cmp(header, 0, 8)) {
errmsg = "The file does not appear to be a PNG image";
fclose(fp);
return Bitmap();
}
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
(png_voidp) nullptr, nullptr, nullptr);
if (!png_ptr) {
errmsg = pngerr;
fclose(fp);
return Bitmap();
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_read_struct(&png_ptr, (png_infopp) nullptr, (png_infopp) nullptr);
errmsg = pngerr;
return Bitmap();
}
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr);
errmsg = pngerr;
fclose(fp);
return Bitmap();
}
#if PNG_LIBPNG_VER >= 10504
png_set_alpha_mode(png_ptr, PNG_ALPHA_PNG, PNG_GAMMA_LINEAR);
#endif
png_init_io(png_ptr, fp);
png_set_sig_bytes(png_ptr, 8);
png_read_info(png_ptr, info_ptr);
int width = png_get_image_width(png_ptr, info_ptr);
int height = png_get_image_height(png_ptr, info_ptr);
int color_type = png_get_color_type(png_ptr, info_ptr);
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png_ptr);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
png_set_tRNS_to_alpha(png_ptr);
png_set_swap_alpha(png_ptr);
} else
png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_BEFORE);
} else {
if (color_type == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8)
png_set_expand_gray_1_2_4_to_8(png_ptr);
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png_ptr);
if (color_type & PNG_COLOR_MASK_ALPHA)
png_set_swap_alpha(png_ptr);
else
png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_BEFORE);
}
if (png_get_bit_depth(png_ptr, info_ptr) == 16)
#if PNG_LIBPNG_VER >= 10504
png_set_scale_16(png_ptr);
#else
png_set_strip_16(png_ptr);
#endif
png_read_update_info(png_ptr, info_ptr);
if (png_get_bit_depth(png_ptr, info_ptr) != 8) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr);
errmsg = "Depth of PNG image is not eight bits.";
fclose(fp);
return Bitmap();
}
const double mpi = 25.4/1000.0;
dotsPerInch = Vector(mpi * png_get_x_pixels_per_meter(png_ptr, info_ptr),
mpi * png_get_y_pixels_per_meter(png_ptr, info_ptr));
Buffer pixels(4 * width * height);
png_bytep row[height];
for (int y = 0; y < height; ++y)
row[y] = (png_bytep) pixels.data() + 4 * width * y;
png_read_image(png_ptr, row);
png_read_end(png_ptr, (png_infop) nullptr);
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr);
fclose(fp);
Bitmap bm(width, height, Bitmap::ERGB|Bitmap::EAlpha, pixels);
return bm;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipeimage.cpp 0000644 0001750 0001750 00000012412 13561570220 016553 0 ustar otfried otfried // --------------------------------------------------------------------
// The image object.
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipeimage.h"
#include "ipepainter.h"
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::Image
\ingroup obj
\brief The image object.
*/
//! Create a new image
Image::Image(const Rect &rect, Bitmap bitmap)
: Object()
{
iRect = rect;
iBitmap = bitmap;
iOpacity = Attribute::OPAQUE();
assert(!iBitmap.isNull());
}
//! Create from XML stream.
Image::Image(const XmlAttributes &attr, String data)
: Object(attr)
{
init(attr);
iBitmap = Bitmap(attr, data);
}
//! Create from XML stream with given bitmap.
Image::Image(const XmlAttributes &attr, Bitmap bitmap)
: Object(attr), iBitmap(bitmap)
{
init(attr);
}
void Image::init(const XmlAttributes &attr)
{
String str;
if (attr.has("opacity", str))
iOpacity = Attribute(true, str);
else
iOpacity = Attribute::OPAQUE();
// parse rect
Lex st(attr["rect"]);
Vector v;
st >> v.x >> v.y;
iRect.addPoint(v);
st >> v.x >> v.y;
iRect.addPoint(v);
}
//! Clone object
Object *Image::clone() const
{
return new Image(*this);
}
//! Return pointer to this object.
Image *Image::asImage()
{
return this;
}
Object::Type Image::type() const
{
return EImage;
}
//! Call VisitImage of visitor.
void Image::accept(Visitor &visitor) const
{
visitor.visitImage(this);
}
//! Save image in XML stream.
void Image::saveAsXml(Stream &stream, String layer) const
{
stream << "\n";
}
//! Draw image.
void Image::draw(Painter &painter) const
{
Matrix m(iRect.width(), 0, 0, iRect.height(),
iRect.bottomLeft().x, iRect.bottomLeft().y);
painter.pushMatrix();
painter.transform(matrix());
painter.untransform(transformations());
painter.transform(m);
painter.push();
painter.setOpacity(iOpacity);
painter.drawBitmap(iBitmap);
painter.pop();
painter.popMatrix();
}
void Image::drawSimple(Painter &painter) const
{
painter.pushMatrix();
painter.transform(matrix());
painter.untransform(transformations());
painter.newPath();
painter.rect(iRect);
painter.drawPath(EStrokedOnly);
painter.popMatrix();
}
double Image::distance(const Vector &v, const Matrix &m, double bound) const
{
Matrix m1 = m * matrix();
Vector u[5];
u[0] = m1 * iRect.bottomLeft();
u[1] = m1 * iRect.bottomRight();
u[2] = m1 * iRect.topRight();
u[3] = m1 * iRect.topLeft();
u[4] = u[0];
Rect box;
for (int i = 0; i < 4; ++i)
box.addPoint(u[i]);
if (box.certainClearance(v, bound))
return bound;
double d = bound;
double d1;
for (int i = 0; i < 4; ++i) {
if ((d1 = Segment(u[i], u[i+1]).distance(v, d)) < d)
d = d1;
}
return d;
}
void Image::addToBBox(Rect &box, const Matrix &m, bool) const
{
Matrix m1 = m * matrix();
box.addPoint(m1 * iRect.bottomLeft());
box.addPoint(m1 * iRect.bottomRight());
box.addPoint(m1 * iRect.topRight());
box.addPoint(m1 * iRect.topLeft());
}
void Image::snapCtl(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
Matrix m1 = m * matrix();
(m1 * iRect.bottomLeft()).snap(mouse, pos, bound);
(m1 * iRect.bottomRight()).snap(mouse, pos, bound);
(m1 * iRect.topRight()).snap(mouse, pos, bound);
(m1 * iRect.topLeft()).snap(mouse, pos, bound);
}
//! Set opacity of the object.
void Image::setOpacity(Attribute opaq)
{
iOpacity = opaq;
}
bool Image::setAttribute(Property prop, Attribute value)
{
switch (prop) {
case EPropOpacity:
if (value != opacity()) {
setOpacity(value);
return true;
}
break;
default:
return Object::setAttribute(prop, value);
}
return false;
}
Attribute Image::getAttribute(Property prop) const noexcept
{
switch (prop) {
case EPropOpacity:
return opacity();
default:
return Object::getAttribute(prop);
}
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipereference.cpp 0000644 0001750 0001750 00000026540 13561570220 017436 0 ustar otfried otfried // --------------------------------------------------------------------
// The reference object.
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipereference.h"
#include "ipestyle.h"
#include "ipepainter.h"
using namespace ipe;
/*! \class ipe::Reference
\ingroup obj
\brief The reference object.
A Reference uses a symbol, that is, an object defined in an Ipe
StyleSheet. The object is defined as a named symbol in the style
sheet, and can be reused arbitrarily often in the document. This
can, for instance, be used for backgrounds on multi-page documents.
It is admissible to refer to an undefined object (that is, the
current style sheet cascade does not define a symbol with the given
name). Nothing will be drawn in this case.
The Reference has a stroke, fill, and pen attribute. When drawing a
symbol, these attributes are made available to the symbol through
the names "sym-stroke", "sym-fill", and "sym-pen". These are not
defined by the style sheet, but resolved by the Painter when the
symbol sets its attributes.
Note that it is not possible to determine \e whether a symbol is
filled from the Reference object.
The size attribute is of type ESymbolSize, and indicates a
magnification factor applied to the symbol. This magnification is
applied after the untransformation indicated in the Reference and in
the Symbol has been performed, so that symbols are magnified even if
they specify ETransformationsTranslations.
The size is meant for symbols such as marks, that can be shown in
different sizes. Another application of symbols is for backgrounds
and logos. Their size should not be changed when the user changes
the symbolsize for the entire page. For such symbols, the size
attribute of the Reference should be set to the absolute value zero.
This means that no magnification is applied to the object, and it
also \e stops setAttribute() from modifying the size. (The size can
still be changed using setSize(), but this is not available from
Lua.)
*/
//! Create a reference to the named object in stylesheet.
Reference::Reference(const AllAttributes &attr, Attribute name, Vector pos)
: Object()
{
assert(name.isSymbolic());
iName = name;
iPos = pos;
iPen = Attribute::NORMAL();
iSize = Attribute::ONE();
iStroke = Attribute::BLACK();
iFill = Attribute::WHITE();
iFlags = flagsFromName(name.string());
if (iFlags & EHasPen)
iPen = attr.iPen;
if (iFlags & EHasSize)
iSize = attr.iSymbolSize;
if (iFlags & EHasStroke)
iStroke = attr.iStroke;
if (iFlags & EHasFill)
iFill = attr.iFill;
}
//! Create from XML stream.
Reference::Reference(const XmlAttributes &attr, String /* data */)
: Object(attr)
{
iName = Attribute(true, attr["name"]);
String str;
if (attr.has("pos", str)) {
Lex st(str);
st >> iPos.x >> iPos.y;
} else
iPos = Vector::ZERO;
iPen = Attribute::makeScalar(attr["pen"], Attribute::NORMAL());
iSize = Attribute::makeScalar(attr["size"], Attribute::ONE());
iStroke = Attribute::makeColor(attr["stroke"], Attribute::BLACK());
iFill = Attribute::makeColor(attr["fill"], Attribute::WHITE());
iFlags = flagsFromName(iName.string());
}
//! Clone object
Object *Reference::clone() const
{
return new Reference(*this);
}
//! Return pointer to this object.
Reference *Reference::asReference()
{
return this;
}
Object::Type Reference::type() const
{
return EReference;
}
//! Call visitReference of visitor.
void Reference::accept(Visitor &visitor) const
{
visitor.visitReference(this);
}
//! Save in XML format.
void Reference::saveAsXml(Stream &stream, String layer) const
{
stream << "\n";
}
//! Draw reference.
/*! If the symbolic attribute is not defined in the current style sheet,
nothing is drawn at all. */
void Reference::draw(Painter &painter) const
{
const Symbol *symbol = painter.cascade()->findSymbol(iName);
if (symbol) {
iSnap = symbol->iSnap; // cache snap point information
Attribute si = painter.cascade()->find(ESymbolSize, iSize);
double s = si.number().toDouble();
painter.pushMatrix();
painter.transform(matrix());
painter.translate(iPos);
painter.untransform(transformations());
painter.untransform(symbol->iTransformations);
if (iFlags & EHasSize) {
Matrix m(s, 0, 0, s, 0, 0);
painter.transform(m);
}
painter.push();
if (iFlags & EHasStroke)
painter.setSymStroke(iStroke);
if (iFlags & EHasFill)
painter.setSymFill(iFill);
if (iFlags & EHasPen)
painter.setSymPen(iPen);
painter.drawSymbol(iName);
painter.pop();
painter.popMatrix();
}
}
void Reference::drawSimple(Painter &painter) const
{
painter.pushMatrix();
painter.transform(matrix());
painter.translate(iPos);
if (iSnap.size() > 0) {
const Symbol *symbol = painter.cascade()->findSymbol(iName);
if (symbol) {
painter.untransform(symbol->iTransformations);
if (iFlags & EHasSize) {
Attribute si = painter.cascade()->find(ESymbolSize, iSize);
double s = si.number().toDouble();
Matrix m(s, 0, 0, s, 0, 0);
painter.transform(m);
}
painter.push();
symbol->iObject->drawSimple(painter);
painter.pop();
painter.popMatrix();
return;
}
}
painter.untransform(ETransformationsTranslations);
const int size = 10;
painter.newPath();
painter.moveTo(Vector(-size, 0));
painter.lineTo(Vector(size, 0));
painter.moveTo(Vector(0, -size));
painter.lineTo(Vector(0, size));
painter.drawPath(EStrokedOnly);
painter.popMatrix();
}
/*! \copydoc Object::addToBBox
This only adds the position (or the snap positions) to the \a box. */
void Reference::addToBBox(Rect &box, const Matrix &m, bool cp) const
{
if (iSnap.size() > 0) {
for (const Vector & pos : iSnap)
box.addPoint((m * matrix()) * (iPos + pos));
} else
box.addPoint((m * matrix()) * iPos);
}
void Reference::checkStyle(const Cascade *sheet, AttributeSeq &seq) const
{
const Symbol *symbol = sheet->findSymbol(iName);
if (!symbol) {
if (std::find(seq.begin(), seq.end(), iName) == seq.end())
seq.push_back(iName);
} else {
iSnap = symbol->iSnap; // cache snap positions
}
if (iFlags & EHasStroke)
checkSymbol(EColor, iStroke, sheet, seq);
if (iFlags & EHasFill)
checkSymbol(EColor, iFill, sheet, seq);
if (iFlags & EHasPen)
checkSymbol(EPen, iPen, sheet, seq);
if (iFlags & EHasSize)
checkSymbol(ESymbolSize, iSize, sheet, seq);
}
double Reference::distance(const Vector &v, const Matrix &m, double bound) const
{
if (iSnap.size() > 0) {
double d = bound;
for (const Vector & snapPos : iSnap) {
double d1 = (v - (m * (matrix() * (iPos + snapPos)))).len();
if (d1 < d)
d = d1;
}
return d;
} else
return (v - (m * (matrix() * iPos))).len();
}
void Reference::snapVtx(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
if (iSnap.size() > 0) {
for (const Vector & snapPos : iSnap)
(m * (matrix() * (iPos + snapPos))).snap(mouse, pos, bound);
} else
(m * (matrix() * iPos)).snap(mouse, pos, bound);
}
void Reference::snapBnd(const Vector &, const Matrix &,
Vector &, double &) const
{
// nothing
}
//! Set name of symbol referenced.
void Reference::setName(Attribute name)
{
iName = name;
iFlags = flagsFromName(name.string());
}
//! Set pen.
void Reference::setPen(Attribute pen)
{
iPen = pen;
}
//! Set stroke color.
void Reference::setStroke(Attribute color)
{
iStroke = color;
}
//! Set fill color.
void Reference::setFill(Attribute color)
{
iFill = color;
}
//! Set size (magnification) of symbol.
void Reference::setSize(Attribute size)
{
iSize = size;
}
//! \copydoc Object::setAttribute
bool Reference::setAttribute(Property prop, Attribute value)
{
switch (prop) {
case EPropPen:
if ((iFlags & EHasPen) && value != pen()) {
setPen(value);
return true;
}
break;
case EPropStrokeColor:
if ((iFlags & EHasStroke) && value != stroke()) {
setStroke(value);
return true;
}
break;
case EPropFillColor:
if ((iFlags & EHasFill) && value != fill()) {
setFill(value);
return true;
}
break;
case EPropSymbolSize:
if ((iFlags & EHasSize) && value != size()) {
setSize(value);
return true;
}
break;
case EPropMarkShape:
if ((iFlags & EIsMark) && value != name()) {
setName(value);
return true;
}
break;
default:
return Object::setAttribute(prop, value);
}
return false;
}
Attribute Reference::getAttribute(Property prop) const noexcept
{
switch (prop) {
case EPropPen:
if (iFlags & EHasPen)
return pen();
break;
case EPropStrokeColor:
if (iFlags & EHasStroke)
return stroke();
break;
case EPropFillColor:
if (iFlags & EHasFill)
return fill();
break;
case EPropSymbolSize:
if (iFlags & EHasSize)
return size();
break;
case EPropMarkShape:
if (iFlags & EIsMark)
return name();
break;
default:
break;
}
return Object::getAttribute(prop);
}
// --------------------------------------------------------------------
uint32_t Reference::flagsFromName(String name)
{
uint32_t flags = 0;
if (name.left(5) == "mark/")
flags |= EIsMark;
if (name.left(6) == "arrow/")
flags |= EIsArrow;
int i = name.rfind('(');
if (i < 0 || name[name.size() - 1] != ')')
return flags;
String letters = name.substr(i+1, name.size() - i - 2);
if (letters.find('x') >= 0)
flags |= EHasSize;
if (letters.find('s') >= 0)
flags |= EHasStroke;
if (letters.find('f') >= 0)
flags |= EHasFill;
if (letters.find('p') >= 0)
flags |= EHasPen;
return flags;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipestdstyles.cpp 0000644 0001750 0001750 00000006633 13561570220 017537 0 ustar otfried otfried // --------------------------------------------------------------------
// Standard Ipe style (embedded in Ipelib)
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipebase.h"
#include "ipestyle.h"
#include "ipeiml.h"
using namespace ipe;
static const char *styleStandard[] = {
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"0 0 m -1.0 0.333 l -1.0 -0.333 l h",
"",
"\n",
"",
"",
"",
"",
"",
"",
"",
"",
nullptr };
class StandardStyleSource : public DataSource {
public:
StandardStyleSource(const char **lines)
: iLine(lines), iChar(lines[0]) { /* nothing */ }
int getChar();
private:
const char **iLine;
const char *iChar;
};
int StandardStyleSource::getChar()
{
if (!*iLine)
return EOF;
// not yet at end of data
if (!*iChar) {
iLine++;
iChar = *iLine;
return '\n'; // important: iChar may be 0 now!
}
return *iChar++;
}
//! Create standard built-in style sheet.
StyleSheet *StyleSheet::standard()
{
// ipeDebug("creating standard stylesheet");
StandardStyleSource source(styleStandard);
ImlParser parser(source);
StyleSheet *sheet = parser.parseStyleSheet();
assert(sheet);
sheet->iStandard = true;
sheet->iName = "standard";
return sheet;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipebitmap.cpp 0000644 0001750 0001750 00000053615 13561570220 016757 0 ustar otfried otfried // --------------------------------------------------------------------
// Bitmaps
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipebitmap.h"
#include "ipeutils.h"
#include
using namespace ipe;
extern bool dctDecode(Buffer dctData, Buffer pixelData);
// --------------------------------------------------------------------
/*! \class ipe::Bitmap
\ingroup base
\brief A bitmap.
Bitmaps are explicitely shared using reference-counting. Copying is
cheap, so Bitmap objects are meant to be passed by value.
The bitmap provides a slot for short-term storage of an "object
number". The PDF embedder, for instance, sets it to the PDF object
number when embedding the bitmap, and can reuse it when "drawing"
the bitmap.
*/
//! Default constructor constructs null bitmap.
Bitmap::Bitmap()
{
iImp = nullptr;
}
//! Create from XML stream.
Bitmap::Bitmap(const XmlAttributes &attr, String pcdata)
{
auto lengths = init(attr);
int length = lengths.first;
if (length == 0)
length = height() * width() * (isGray() ? 1 : 3);
int alphaLength = lengths.second;
// decode data
iImp->iData = Buffer(length);
char *p = iImp->iData.data();
Buffer alpha;
char *q = nullptr;
if (alphaLength > 0) {
alpha = Buffer(alphaLength);
q = alpha.data();
}
if (attr["encoding"] == "base64") {
Buffer dbuffer(pcdata.data(), pcdata.size());
BufferSource source(dbuffer);
Base64Source b64source(source);
while (length-- > 0)
*p++ = b64source.getChar();
while (alphaLength-- > 0)
*q++ = b64source.getChar();
} else {
Lex datalex(pcdata);
while (length-- > 0)
*p++ = char(datalex.getHexByte());
while (alphaLength-- > 0)
*q++ = char(datalex.getHexByte());
}
unpack(alpha);
computeChecksum();
analyze();
}
//! Create from XML using external raw data
Bitmap::Bitmap(const XmlAttributes &attr, Buffer data, Buffer alpha)
{
init(attr);
iImp->iData = data;
unpack(alpha);
computeChecksum();
analyze();
}
std::pair Bitmap::init(const XmlAttributes &attr)
{
iImp = new Imp;
iImp->iRefCount = 1;
iImp->iFlags = 0;
iImp->iColorKey = -1;
iImp->iPixelsComputed = false;
iImp->iObjNum = Lex(attr["id"]).getInt();
iImp->iWidth = Lex(attr["width"]).getInt();
iImp->iHeight = Lex(attr["height"]).getInt();
int length = Lex(attr["length"]).getInt();
int alphaLength = Lex(attr["alphaLength"]).getInt();
assert(iImp->iWidth > 0 && iImp->iHeight > 0);
String cs = attr["ColorSpace"];
if (cs.right(5) =="Alpha") {
iImp->iFlags |= EAlpha;
cs = cs.left(cs.size() - 5);
}
if (cs == "DeviceRGB")
iImp->iFlags |= ERGB;
String fi = attr["Filter"];
if (fi == "DCTDecode")
iImp->iFlags |= EDCT;
else if (fi == "FlateDecode")
iImp->iFlags |= EInflate;
String cc;
if (!isJpeg() && attr.has("ColorKey", cc))
iImp->iColorKey = Lex(cc).getHexNumber();
return std::make_pair(length, alphaLength);
}
//! Create a new image from given image data.
/*! If you already have data in native-endian ARGB32 without premultiplication,
pass it with flag ENative.
Otherwise pass a byte stream and set ERGB and EAlpha correctly:
EAlpha: each pixel starts with one byte of alpha channel,
ERGB: each pixel has three bytes of R, G, B, in this order,
otherwise each pixel has one byte of gray value. */
Bitmap::Bitmap(int width, int height, uint32_t flags, Buffer data)
{
iImp = new Imp;
iImp->iRefCount = 1;
iImp->iFlags = flags;
iImp->iColorKey = -1;
iImp->iObjNum = -1;
iImp->iWidth = width;
iImp->iHeight = height;
iImp->iData = data;
iImp->iPixelsComputed = false;
assert(iImp->iWidth > 0 && iImp->iHeight > 0);
unpack(Buffer());
computeChecksum();
analyze();
}
//! Take care of inflating, converting grayscale to rgb, and merging the alpha channel
void Bitmap::unpack(Buffer alphaChannel)
{
if (isJpeg() || iImp->iFlags & ENative)
return;
int npixels = width() * height();
if (iImp->iFlags & EInflate) {
// inflate data
int components = isGray() ? 1 : 3;
if (hasAlpha() && alphaChannel.size() == 0)
components += 1;
uLongf inflatedSize = npixels * components;
Buffer inflated(inflatedSize);
assert(uncompress((Bytef *) inflated.data(), &inflatedSize,
(const Bytef *) iImp->iData.data(), iImp->iData.size()) == Z_OK);
iImp->iData = inflated;
if (alphaChannel.size() > 0) {
inflatedSize = npixels;
Buffer inflatedAlpha(inflatedSize);
assert(uncompress((Bytef *) inflatedAlpha.data(), &inflatedSize,
(const Bytef *) alphaChannel.data(), alphaChannel.size()) == Z_OK);
alphaChannel = inflatedAlpha;
}
}
// convert data to ARGB32 format
bool alphaInMain = hasAlpha() && alphaChannel.size() == 0;
Buffer pixels(npixels * sizeof(uint32_t));
const char *p = iImp->iData.data();
uint32_t *q = (uint32_t *) pixels.data();
uint32_t *fin = q + npixels;
if (!isGray()) {
while (q < fin) {
uint8_t alpha = (alphaInMain ? uint8_t(*p++) : 0xff);
uint8_t r = uint8_t(*p++);
uint8_t g = uint8_t(*p++);
uint8_t b = uint8_t(*p++);
uint32_t pixel = (alpha << 24) | (r << 16) | (g << 8) | b;
*q++ = pixel;
}
} else {
while (q < fin) {
uint8_t alpha = (alphaInMain ? uint8_t(*p++) : 0xff);
uint8_t r = uint8_t(*p++);
*q++ = (alpha << 24) | (r << 16) | (r << 8) | r;
}
}
// merge separate alpha channel
if (hasAlpha() && alphaChannel.size() > 0) {
q = (uint32_t *) pixels.data();
p = alphaChannel.data();
uint32_t pixel;
while (q < fin) {
pixel = *q;
pixel = (pixel & 0x00ffffff) | (*p++ << 24);
*q++ = pixel;
}
}
if (iImp->iColorKey >= 0) {
uint32_t colorKey = (iImp->iColorKey | 0xff000000);
q = (uint32_t *) pixels.data();
while (q < fin) {
if (*q == colorKey)
*q = iImp->iColorKey;
++q;
}
}
iImp->iData = pixels;
}
//! Determine if bitmap has alpha channel, colorkey, rgb values (does nothing for JPG).
void Bitmap::analyze()
{
iImp->iColorKey = -1;
iImp->iFlags &= EDCT|ERGB; // clear all other flags, we recompute them
if (isJpeg())
return;
iImp->iFlags &= EDCT; // ERGB will also be recomputed
const uint32_t *q = (const uint32_t *) iImp->iData.data();
const uint32_t *fin = q + width() * height();
uint32_t pixel, gray;
while (q < fin) {
pixel = *q++ & 0x00ffffff;
gray = (pixel & 0xff);
gray |= (gray << 8) | (gray << 16);
if (pixel != gray) {
iImp->iFlags |= ERGB;
break;
}
}
int candidate = -1, color;
uint32_t alpha;
q = (const uint32_t *) iImp->iData.data();
while (q < fin) {
pixel = *q++;
alpha = pixel & 0xff000000;
color = pixel & 0x00ffffff;
if (alpha != 0 && alpha != 0xff000000) {
iImp->iFlags |= EAlpha;
return;
}
if (alpha == 0) { // transparent color found
if (candidate < 0)
candidate = color;
else if (candidate != color) { // two different transparent colors
iImp->iFlags |= EAlpha;
return;
}
} else if (color == candidate) { // opaque copy of candidate found
iImp->iFlags |= EAlpha;
return;
}
}
iImp->iColorKey = candidate;
}
//! Copy constructor.
/*! Since Bitmaps are reference counted, this is very fast. */
Bitmap::Bitmap(const Bitmap &rhs)
{
iImp = rhs.iImp;
if (iImp)
iImp->iRefCount++;
}
//! Destructor.
Bitmap::~Bitmap()
{
if (iImp && --iImp->iRefCount == 0) {
delete iImp;
}
}
//! Assignment operator (takes care of reference counting).
/*! Very fast. */
Bitmap &Bitmap::operator=(const Bitmap &rhs)
{
if (this != &rhs) {
if (iImp && --iImp->iRefCount == 0)
delete iImp;
iImp = rhs.iImp;
if (iImp)
iImp->iRefCount++;
}
return *this;
}
//! Save bitmap in XML stream.
void Bitmap::saveAsXml(Stream &stream, int id, int pdfObjNum) const
{
assert(iImp);
stream << "= 0) {
char buf[10];
sprintf(buf, "%x", colorKey());
stream << " ColorKey=\"" << buf << "\"";
}
if (pdfObjNum >= 0) {
stream << " pdfObject=\"" << pdfObjNum;
if (hasAlpha())
stream << " " << pdfObjNum-1;
stream << "\"/>\n";
} else {
// save data
auto data = embed();
stream << " length=\"" << data.first.size() << "\"";
if (hasAlpha())
stream << " alphaLength=\"" << data.second.size() << "\"";
stream << " encoding=\"base64\">\n";
Base64Stream b64(stream);
for (const auto & buffer : { data.first, data.second } ) {
const char *p = buffer.data();
const char *fin = p + buffer.size();
while (p != fin)
b64.putChar(*p++);
}
b64.close();
stream << "\n";
}
}
bool Bitmap::equal(Bitmap rhs) const
{
if (iImp == rhs.iImp)
return true;
if (!iImp || !rhs.iImp)
return false;
if (iImp->iFlags != rhs.iImp->iFlags ||
iImp->iWidth != rhs.iImp->iWidth ||
iImp->iHeight != rhs.iImp->iHeight ||
iImp->iChecksum != rhs.iImp->iChecksum ||
iImp->iData.size() != rhs.iImp->iData.size())
return false;
// check actual data
int len = iImp->iData.size();
char *p = iImp->iData.data();
char *q = rhs.iImp->iData.data();
while (len--) {
if (*p++ != *q++)
return false;
}
return true;
}
void Bitmap::computeChecksum()
{
int s = 0;
int len = iImp->iData.size();
char *p = iImp->iData.data();
while (len--) {
s = (s & 0x0fffffff) << 3;
s += *p++;
}
iImp->iChecksum = s;
}
//! Create the data to be embedded in an XML or PDF file.
/*! For Jpeg images, this is simply the bitmap data. For other
images, rgb/grayscale data and alpha channel are split and deflated
separately. */
std::pair Bitmap::embed() const
{
if (isJpeg())
return std::make_pair(iImp->iData, Buffer());
int npixels = width() * height();
uint32_t *src = (uint32_t *) iImp->iData.data();
uint32_t *fin = src + npixels;
uint32_t pixel;
Buffer rgb(npixels * (isGray() ? 1 : 3));
char *p = rgb.data();
while (src < fin) {
pixel = *src++;
if (isGray()) {
*p++ = pixel & 0xff;
} else {
*p++ = (pixel & 0xff0000) >> 16;
*p++ = (pixel & 0x00ff00) >> 8;
*p++ = (pixel & 0x0000ff);
}
}
int deflatedSize;
Buffer deflated = DeflateStream::deflate(rgb.data(), rgb.size(), deflatedSize, 9);
rgb = Buffer(deflated.data(), deflatedSize);
Buffer alpha;
if (hasAlpha()) {
alpha = Buffer(npixels);
src = (uint32_t *) iImp->iData.data();
p = alpha.data();
while (src < fin)
*p++ = (*src++ & 0xff000000) >> 24;
deflated = DeflateStream::deflate(alpha.data(), alpha.size(), deflatedSize, 9);
alpha = Buffer(deflated.data(), deflatedSize);
}
return std::make_pair(rgb, alpha);
}
// --------------------------------------------------------------------
void Bitmap::savePixels(const char *fname)
{
FILE *file = Platform::fopen(fname, "wb");
if (!file)
return;
if (isJpeg()) {
fwrite(iImp->iData.data(), 1, iImp->iData.size(), file);
} else {
fprintf(file, "PyRGBA\n%d %d\n255\n", width(), height());
Buffer pixels = Buffer(iImp->iData.size());
uint32_t *p = (uint32_t *) iImp->iData.data();
uint8_t *q = (uint8_t *) pixels.data();
uint32_t *fin = p + width() * height();
while (p < fin) {
uint32_t pixel = *p++;
*q++ = (pixel & 0x00ff0000) >> 16;
*q++ = (pixel & 0x0000ff00) >> 8;
*q++ = (pixel & 0x000000ff);
*q++ = (pixel & 0xff000000) >> 24;
}
fwrite(pixels.data(), 1, iImp->iData.size(), file);
}
fclose(file);
}
// --------------------------------------------------------------------
//! Return pixels for rendering.
/*! Returns empty buffer if it cannot decode the bitmap information.
Otherwise, returns a buffer of size width() * height() uint32_t's.
The data is in cairo ARGB32 format, that is native-endian uint32_t's
with premultiplied alpha.
*/
Buffer Bitmap::pixelData()
{
if (!iImp->iPixelsComputed) {
iImp->iPixelsComputed = true;
if (isJpeg()) {
Buffer stream = iImp->iData;
Buffer pixels;
pixels = Buffer(4 * width() * height());
if (!dctDecode(stream, pixels))
return Buffer();
iImp->iPixelData = pixels;
} else {
if (hasAlpha() || colorKey() >= 0) {
// premultiply RGB data
iImp->iPixelData = Buffer(iImp->iData.size());
uint32_t *p = (uint32_t *) iImp->iData.data();
uint32_t *q = (uint32_t *) iImp->iPixelData.data();
uint32_t *fin = p + width() * height();
uint32_t pixel, alpha, alphaM, r, g, b;
while (p < fin) {
pixel = *p++;
alpha = (pixel & 0xff000000);
alphaM = alpha >> 24;
r = alphaM * (pixel & 0xff0000) / 255;
g = alphaM * (pixel & 0x00ff00) / 255;
b = alphaM * (pixel & 0x0000ff) / 255;
*q++ = alpha | (r & 0xff0000) | (g & 0x00ff00) | (b & 0x0000ff);
}
} else
iImp->iPixelData = iImp->iData;
}
}
return iImp->iPixelData;
}
// --------------------------------------------------------------------
/*
JPG reading code
Copyright (c) 1996-2002 Han The Thanh,
This code is part of pdfTeX.
pdfTeX is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
*/
#define JPG_GRAY 1 /* Gray color space, use /DeviceGray */
#define JPG_RGB 3 /* RGB color space, use /DeviceRGB */
#define JPG_CMYK 4 /* CMYK color space, use /DeviceCMYK */
enum JPEG_MARKER { /* JPEG marker codes */
M_SOF0 = 0xc0, /* baseline DCT */
M_SOF1 = 0xc1, /* extended sequential DCT */
M_SOF2 = 0xc2, /* progressive DCT */
M_SOF3 = 0xc3, /* lossless (sequential) */
M_SOF5 = 0xc5, /* differential sequential DCT */
M_SOF6 = 0xc6, /* differential progressive DCT */
M_SOF7 = 0xc7, /* differential lossless */
M_JPG = 0xc8, /* JPEG extensions */
M_SOF9 = 0xc9, /* extended sequential DCT */
M_SOF10 = 0xca, /* progressive DCT */
M_SOF11 = 0xcb, /* lossless (sequential) */
M_SOF13 = 0xcd, /* differential sequential DCT */
M_SOF14 = 0xce, /* differential progressive DCT */
M_SOF15 = 0xcf, /* differential lossless */
M_DHT = 0xc4, /* define Huffman tables */
M_DAC = 0xcc, /* define arithmetic conditioning table */
M_RST0 = 0xd0, /* restart */
M_RST1 = 0xd1, /* restart */
M_RST2 = 0xd2, /* restart */
M_RST3 = 0xd3, /* restart */
M_RST4 = 0xd4, /* restart */
M_RST5 = 0xd5, /* restart */
M_RST6 = 0xd6, /* restart */
M_RST7 = 0xd7, /* restart */
M_SOI = 0xd8, /* start of image */
M_EOI = 0xd9, /* end of image */
M_SOS = 0xda, /* start of scan */
M_DQT = 0xdb, /* define quantization tables */
M_DNL = 0xdc, /* define number of lines */
M_DRI = 0xdd, /* define restart interval */
M_DHP = 0xde, /* define hierarchical progression */
M_EXP = 0xdf, /* expand reference image(s) */
M_APP0 = 0xe0, /* application marker, used for JFIF */
M_APP1 = 0xe1, /* application marker */
M_APP2 = 0xe2, /* application marker */
M_APP3 = 0xe3, /* application marker */
M_APP4 = 0xe4, /* application marker */
M_APP5 = 0xe5, /* application marker */
M_APP6 = 0xe6, /* application marker */
M_APP7 = 0xe7, /* application marker */
M_APP8 = 0xe8, /* application marker */
M_APP9 = 0xe9, /* application marker */
M_APP10 = 0xea, /* application marker */
M_APP11 = 0xeb, /* application marker */
M_APP12 = 0xec, /* application marker */
M_APP13 = 0xed, /* application marker */
M_APP14 = 0xee, /* application marker, used by Adobe */
M_APP15 = 0xef, /* application marker */
M_JPG0 = 0xf0, /* reserved for JPEG extensions */
M_JPG13 = 0xfd, /* reserved for JPEG extensions */
M_COM = 0xfe, /* comment */
M_TEM = 0x01, /* temporary use */
};
inline int read2bytes(FILE *f)
{
uint8_t c1 = fgetc(f);
uint8_t c2 = fgetc(f);
return (c1 << 8) + c2;
}
// --------------------------------------------------------------------
//! Read information about JPEG image from file.
/*! Returns NULL on success, an error message otherwise. Sets flags
to EDCT and possibly ERGB. */
const char *Bitmap::readJpegInfo(FILE *file, int &width, int &height,
Vector &dotsPerInch, uint32_t &flags)
{
static char jpg_id[] = "JFIF";
bool app0_seen = false;
dotsPerInch = Vector(0, 0);
flags = EDCT;
if (read2bytes(file) != 0xFFD8) {
return "The file does not appear to be a JPEG image";
}
for (;;) {
int ch = fgetc(file);
if (ch != 0xff)
return "Reading JPEG image failed";
do {
ch = fgetc(file);
} while (ch == 0xff);
ipeDebug("JPEG tag %x", ch & 0xff);
int fpos = ftell(file);
switch (ch & 0xff) {
case M_SOF5:
case M_SOF6:
case M_SOF7:
case M_SOF9:
case M_SOF10:
case M_SOF11:
case M_SOF13:
case M_SOF14:
case M_SOF15:
return "Unsupported type of JPEG compression";
case M_SOF0:
case M_SOF1:
case M_SOF2: // progressive DCT allowed since PDF-1.3
case M_SOF3:
read2bytes(file); /* read segment length */
ch = fgetc(file);
if (ch != 8)
return "Unsupported bit width of pixels in JPEG image";
height = read2bytes(file);
width = read2bytes(file);
ch = fgetc(file);
switch (ch & 0xff) {
case JPG_GRAY:
break;
case JPG_RGB:
flags |= ERGB;
break;
default:
return "Unsupported color space in JPEG image";
}
fseek(file, 0, SEEK_SET);
return nullptr; // success!
case M_APP0: {
int len = read2bytes(file);
if (app0_seen) {
fseek(file, fpos + len, SEEK_SET);
break;
}
for (int i = 0; i < 5; i++) {
ch = fgetc(file);
if (ch != jpg_id[i]) {
return "Reading JPEG image failed";
}
}
read2bytes(file); // JFIF version
char units = fgetc(file);
int xres = read2bytes(file);
int yres = read2bytes(file);
if (xres != 0 && yres != 0) {
switch (units) {
case 1: /* pixels per inch */
dotsPerInch = Vector(xres, yres);
break;
case 2: /* pixels per cm */
dotsPerInch = Vector(xres * 2.54, yres * 2.54);
break;
default: // 0: aspect ratio only
break;
}
}
app0_seen = true;
fseek(file, fpos + len, SEEK_SET);
break; }
case M_SOI: // ignore markers without parameters
case M_EOI:
case M_TEM:
case M_RST0:
case M_RST1:
case M_RST2:
case M_RST3:
case M_RST4:
case M_RST5:
case M_RST6:
case M_RST7:
break;
default: // skip variable length markers
fseek(file, fpos + read2bytes(file), SEEK_SET);
break;
}
}
}
//! Read JPEG image from file.
/*! Returns the image as a DCT-encoded Bitmap.
Sets \a dotsPerInch if the image file contains a resolution,
otherwise sets it to (0,0).
If reading the file fails, returns a null Bitmap,
and sets the error message \a errmsg.
*/
Bitmap Bitmap::readJpeg(const char *fname, Vector &dotsPerInch, const char * &errmsg)
{
FILE *file = Platform::fopen(fname, "rb");
if (!file) {
errmsg = "Error opening file";
return Bitmap();
}
int width, height;
uint32_t flags;
errmsg = Bitmap::readJpegInfo(file, width, height, dotsPerInch, flags);
fclose(file);
if (errmsg)
return Bitmap();
String a = Platform::readFile(fname);
return Bitmap(width, height, flags, Buffer(a.data(), a.size()));
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipelatex.cpp 0000644 0001750 0001750 00000034230 13561570220 016610 0 ustar otfried otfried // --------------------------------------------------------------------
// Interface with Pdflatex
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipestyle.h"
#include "ipegroup.h"
#include "ipereference.h"
#include "ipelatex.h"
#include
using namespace ipe;
/*! \class ipe::Latex
\brief Object that converts latex source to PDF format.
This object is responsible for creating the PDF representation of
text objects.
*/
//! Create a converter object.
Latex::Latex(const Cascade *sheet, LatexType latexType)
{
iCascade = sheet;
iResources = new PdfResources;
iLatexType = latexType;
iXetex = (latexType == LatexType::Xetex);
}
//! Destructor.
Latex::~Latex()
{
for (auto &it : iXForms)
delete it;
delete iResources;
}
// --------------------------------------------------------------------
//! Return the newly created PdfResources and pass ownership to caller.
PdfResources *Latex::takeResources()
{
PdfResources *r = iResources;
iResources = nullptr;
return r;
}
// --------------------------------------------------------------------
class ipe::TextCollectingVisitor : public Visitor {
public:
TextCollectingVisitor(Latex::TextList *list);
virtual void visitText(const Text *obj);
virtual void visitGroup(const Group *obj);
virtual void visitReference(const Reference *obj);
public:
bool iTextFound;
private:
Latex::TextList *iList;
};
TextCollectingVisitor::TextCollectingVisitor(Latex::TextList *list)
: iList(list)
{
// nothing
}
void TextCollectingVisitor::visitText(const Text *obj)
{
Latex::SText s;
s.iText = obj;
s.iSize = obj->size();
iList->push_back(s);
iTextFound = true;
}
void TextCollectingVisitor::visitGroup(const Group *obj)
{
for (Group::const_iterator it = obj->begin(); it != obj->end(); ++it)
(*it)->accept(*this);
}
void TextCollectingVisitor::visitReference(const Reference *)
{
// need to figure out what to do for symbols
}
// --------------------------------------------------------------------
/*! Scan an object and insert all text objects into Latex's list.
Returns total number of text objects found so far. */
int Latex::scanObject(const Object *obj)
{
TextCollectingVisitor visitor(&iTextObjects);
obj->accept(visitor);
return iTextObjects.size();
}
/*! Scan a page and insert all text objects into Latex's list.
Returns total number of text objects found so far. */
int Latex::scanPage(Page *page)
{
page->applyTitleStyle(iCascade);
TextCollectingVisitor visitor(&iTextObjects);
const Text *title = page->titleText();
if (title)
title->accept(visitor);
for (int i = 0; i < page->count(); ++i) {
visitor.iTextFound = false;
page->object(i)->accept(visitor);
if (visitor.iTextFound)
page->invalidateBBox(i);
}
return iTextObjects.size();
}
//! Create Text object to represent the page number of this view.
void Latex::addPageNumber(int pno, int vno, int npages, int nviews)
{
const StyleSheet::PageNumberStyle *pns = iCascade->findPageNumberStyle();
AllAttributes attr;
attr.iStroke = pns->iColor;
attr.iTextSize = pns->iSize;
attr.iHorizontalAlignment = pns->iHorizontalAlignment;
attr.iVerticalAlignment = pns->iVerticalAlignment;
char latex[256];
sprintf(latex, "\\def\\ipeNumber#1#2{#%d}"
"\\setcounter{ipePage}{%d}\\setcounter{ipeView}{%d}"
"\\setcounter{ipePages}{%d}\\setcounter{ipeViews}{%d}",
(nviews > 1 ? 2 : 1), pno + 1, vno + 1, npages, nviews);
String data = pns->iText.empty() ?
"\\ipeNumber{\\arabic{ipePage}}{\\arabic{ipePage} - \\arabic{ipeView}}" :
pns->iText;
Text *t = new Text(attr, String(latex) + data, pns->iPos, Text::ELabel);
SText s;
s.iText = t;
s.iSize = t->size();
iTextObjects.push_back(s);
PdfResources::SPageNumber pn;
pn.page = pno;
pn.view = vno;
pn.text.reset(t);
iResources->addPageNumber(pn);
}
/*! Create a Latex source file with all the text objects collected
before. The client should have prepared a directory for the
Pdflatex run, and pass the name of the Latex source file to be
written by Latex.
Returns the number of text objects that did not yet have an XForm,
or a negative error code.
*/
int Latex::createLatexSource(Stream &stream, String preamble)
{
bool ancient = (getenv("IPEANCIENTPDFTEX") != nullptr);
int count = 0;
stream << "\\nonstopmode\n";
if (!iXetex) {
stream << "\\expandafter\\ifx\\csname pdfobjcompresslevel\\endcsname"
<< "\\relax\\else\\pdfobjcompresslevel0\\fi\n";
if (!ancient && iLatexType != LatexType::Luatex)
stream << "\\ifnum\\the\\pdftexversion<140"
<< "\\errmessage{Pdftex is too old. "
<< "Set IPEANCIENTPDFTEX environment variable!}\\fi\n";
if (iLatexType == LatexType::Luatex)
// load luatex85 for new versions of Luatex
stream << "\\expandafter\\ifx\\csname pdfcolorstack\\endcsname\\relax"
<< "\\RequirePackage{luatex85}\\fi\n";
}
stream << "\\documentclass{article}\n"
<< "\\newdimen\\ipefs\n"
<< "\\newcounter{ipePage}\\newcounter{ipeView}\n"
<< "\\newcounter{ipePages}\\newcounter{ipeViews}\n"
<< "\\newcommand{\\PageTitle}[1]{#1}\n"
<< "\\newcommand{\\ipesymbol}[4]{$\\bullet$}\n";
stream << "\\def\\ipedefinecolors#1{\\ipecolorpreamble{#1}\\let\\ipecolorpreamble\\relax}\n"
<< "\\def\\ipecolorpreamble#1{\\usepackage[#1]{xcolor}\n";
AttributeSeq colors;
iCascade->allNames(EColor, colors);
for (AttributeSeq::const_iterator it = colors.begin();
it != colors.end(); ++it) {
// only symbolic names (not black, white, void)
String name = it->string();
Color value = iCascade->find(EColor, *it).color();
if (value.isGray())
stream << "\\definecolor{" << name << "}{gray}{"
<< value.iRed << "}\n";
else
stream << "\\definecolor{" << name << "}{rgb}{"
<< value.iRed << "," << value.iGreen << ","
<< value.iBlue << "}\n";
}
stream << "}\n";
if (iXetex) {
stream << "\\def\\ipesetcolor#1#2#3{\\special{pdf:bc [#1 #2 #3]}}\n"
<< "\\def\\iperesetcolor{\\special{pdf:ec}}\n";
} else if (!ancient) {
stream << "\\makeatletter\n"
<< "\\def\\ipesetcolor#1#2#3{\\def\\current@color{#1 #2 #3 rg #1 #2 #3 RG}"
<< "\\pdfcolorstack\\@pdfcolorstack push{\\current@color}}\n"
<< "\\def\\iperesetcolor{\\pdfcolorstack\\@pdfcolorstack pop}\n"
<< "\\makeatother\n";
} else {
stream << "\\def\\ipesetcolor#1#2#3{\\color[rgb]{#1,#2,#3}}\n"
<< "\\def\\iperesetcolor{}\n";
}
stream << iCascade->findPreamble() << "\n"
<< preamble << "\n"
<< "\\ipedefinecolors{}\n"
<< "\\pagestyle{empty}\n"
<< "\\newcount\\bigpoint\\dimen0=0.01bp\\bigpoint=\\dimen0\n"
<< "\\begin{document}\n"
<< "\\begin{picture}(500,500)\n";
int curnum = 1;
if (iXetex)
stream << "\\special{pdf:obj @ipeforms []}\n";
for (auto &it : iTextObjects) {
const Text *text = it.iText;
if (!text->getXForm())
count++;
Attribute fsAttr = iCascade->find(ETextSize, it.iSize);
// compute x-stretch factor from textstretch
Fixed stretch(1);
if (it.iSize.isSymbolic())
stretch = iCascade->find(ETextStretch, it.iSize).number();
stream << "\\setbox0=\\hbox{";
if (text->isMinipage()) {
stream << "\\begin{minipage}{" <<
text->width()/stretch.toDouble() << "bp}";
}
if (fsAttr.isNumber()) {
Fixed fs = fsAttr.number();
stream << "\\fontsize{" << fs << "}"
<< "{" << fs.mult(6, 5) << "bp}\\selectfont\n";
} else
stream << fsAttr.string() << "\n";
Color col = iCascade->find(EColor, text->stroke()).color();
stream << "\\ipesetcolor{" << col.iRed.toDouble()
<< "}{" << col.iGreen.toDouble()
<< "}{" << col.iBlue.toDouble()
<< "}%\n";
Attribute absStyle =
iCascade->find(text->isMinipage() ? ETextStyle : ELabelStyle,
text->style());
String style = absStyle.string();
int sp = 0;
while (sp < style.size() && style[sp] != '\0')
++sp;
stream << style.substr(0, sp);
String txt = text->text();
stream << txt;
if (text->isMinipage()) {
if (!txt.empty() && txt[txt.size() - 1] != '\n')
stream << "\n";
stream << style.substr(sp + 1);
stream << "\\end{minipage}";
} else
stream << style.substr(sp + 1) << "%\n";
stream << "\\iperesetcolor}\n"
<< "\\count0=\\dp0\\divide\\count0 by \\bigpoint\n";
if (iXetex) {
stream << "\\special{ pdf:bxobj @ipeform" << curnum << "\n"
<< "width \\the\\wd0 \\space "
<< "height \\the\\ht0 \\space "
<< "depth \\the\\dp0}%\n"
<< "\\usebox0%\n"
<< "\\special{pdf:exobj}%\n"
<< "\\special{pdf:obj @ipeinfo" << curnum << " <<"
<< " /IpeId " << curnum
<< " /IpeStretch " << stretch.toDouble()
<< " /IpeDepth \\the\\count0"
<< " /IpeXForm @ipeform" << curnum << " >>}\n"
<< "\\special{pdf:close @ipeinfo" << curnum << "}\n"
<< "\\special{pdf:put @ipeforms @ipeinfo" << curnum << "}\n"
<< "\\put(0,0){\\special{pdf:uxobj @ipeform" << curnum << "}}\n";
} else {
stream << "\\pdfxform attr{/IpeId " << curnum
<< " /IpeStretch " << stretch.toDouble()
<< " /IpeDepth \\the\\count0}"
<< "0\\put(0,0){\\pdfrefxform\\pdflastxform}\n";
}
++curnum;
}
stream << "\\end{picture}\n";
if (iXetex)
stream << "\\special{pdf:close @ipeforms}\n"
<< "\\special{pdf:put @resources << /Ipe @ipeforms >>}\n";
stream << "\\end{document}\n";
return count;
}
bool Latex::getXForm(String key, const PdfDict *ipeInfo)
{
/*
/Type /XObject
/Subtype /Form
/Id /abcd1234
/Depth 246
/Stretch [ 3 3 ]
/BBox [0 0 4.639 4.289]
/FormType 1
/Matrix [1 0 0 1 0 0]
/Resources 11 0 R
*/
Text::XForm *xf = new Text::XForm;
iXForms.push_back(xf);
const PdfObj *xform = iXetex ? ipeInfo->get("IpeXForm", nullptr) :
iResources->findResource("XObject", key);
int xformNum = -1;
if (xform && xform->ref()) {
xformNum = xform->ref()->value();
xform = iResources->object(xformNum);
}
if (!xform || !xform->dict())
return false;
const PdfDict *xformd = xform->dict();
if (iXetex) {
// determine key
const PdfDict *d = iResources->resourcesOfKind("XObject");
for (int i = 0; i < d->count(); ++i) {
const PdfObj *obj = d->value(i);
if (obj->ref() && obj->ref()->value() == xformNum) {
xf->iName = d->key(i);
break;
}
}
if (xf->iName.empty())
return false;
} else {
xf->iName = key;
ipeInfo = xformd;
}
double val;
// Get id
if (!ipeInfo->getNumber("IpeId", val, &iPdf))
return false;
xf->iRefCount = val; // abusing refcount field
if (!ipeInfo->getNumber("IpeDepth", val, &iPdf))
return false;
xf->iDepth = int(val);
if (!ipeInfo->getNumber("IpeStretch", val, &iPdf))
return false;
xf->iStretch = val;
// Get BBox
std::vector a;
if (!xformd->getNumberArray("BBox", &iPdf, a) || a.size() != 4)
return false;
xf->iBBox.addPoint(Vector(a[0], a[1]));
xf->iBBox.addPoint(Vector(a[2], a[3]));
if (!xformd->getNumberArray("Matrix", &iPdf, a) || a.size() != 6)
return false;
if (a[0] != 1.0 || a[1] != 0.0 || a[2] != 0.0 || a[3] != 1.0) {
ipeDebug("PDF XObject has a non-trivial transformation");
return false;
}
xf->iTranslation = Vector(-a[4], -a[5]) - xf->iBBox.bottomLeft();
return true;
}
//! Read the PDF file created by Pdflatex.
/*! Must have performed the call to Pdflatex, and pass the name of the
resulting output file.
*/
bool Latex::readPdf(DataSource &source)
{
if (!iPdf.parse(source)) {
warn("Ipe cannot parse the PDF file produced by Pdflatex.");
return false;
}
const PdfDict *page1 = iPdf.page();
const PdfObj *res = page1->get("Resources", &iPdf);
if (!res || !res->dict())
return false;
if (!iResources->collect(res->dict(), &iPdf))
return false;
if (iXetex) {
const PdfObj *obj = res->dict()->get("Ipe", &iPdf);
if (!obj || !obj->array()) {
warn("Page 1 has no /Ipe link.");
return false;
}
for (int i = 0; i < obj->array()->count(); i++) {
const PdfObj *info = obj->array()->obj(i, &iPdf);
if (!info || !info->dict())
return false;
if (!getXForm(String(), info->dict()))
return false;
}
} else {
const PdfObj *obj = res->dict()->get("XObject", &iPdf);
if (!obj || !obj->dict()) {
warn("Page 1 has no XForms.");
return false;
}
for (int i = 0; i < obj->dict()->count(); i++) {
String key = obj->dict()->key(i);
if (!getXForm(key, nullptr))
return false;
}
}
return true;
}
//! Notify all text objects about their updated PDF code.
/*! Returns true if successful. */
bool Latex::updateTextObjects()
{
int curnum = 1;
for (auto &it : iTextObjects) {
auto xf = std::find_if(iXForms.begin(), iXForms.end(),
[curnum](Text::XForm *f)
{ return f->iRefCount == curnum; } );
if (xf == iXForms.end())
return false;
Text::XForm *xform = *xf;
iXForms.erase(xf);
it.iText->setXForm(xform);
++curnum;
}
return true;
}
/*! Messages about the (mis)behaviour of Pdflatex, probably
incomprehensible to the user. */
void Latex::warn(String msg)
{
ipeDebug(msg.z());
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipepdfparser.cpp 0000644 0001750 0001750 00000057174 13561570220 017475 0 ustar otfried otfried // --------------------------------------------------------------------
// PDF parsing
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipepdfparser.h"
#include "ipeutils.h"
#include
using namespace ipe;
//------------------------------------------------------------------------
// A '1' in this array means the character is white space.
// A '1' or '2' means the character ends a name or command.
// '2' == () {} [] <> / %
static char specialChars[256] = {
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, // 0x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, // 2x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 3x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 5x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 7x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ax
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // bx
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // cx
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // dx
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ex
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx
};
// --------------------------------------------------------------------
inline int toInt(String &s)
{
return std::strtol(s.z(), nullptr, 10);
}
// --------------------------------------------------------------------
/*! \class ipe::PdfObj
* \ingroup base
* \brief Abstract base class for PDF objects.
*/
//! Pure virtual destructor.
PdfObj::~PdfObj()
{
// nothing
}
//! Return this object as PDF null object.
const PdfNull *PdfObj::null() const noexcept { return nullptr; }
//! Return this object as PDF bool object.
const PdfBool *PdfObj::boolean() const noexcept { return nullptr; }
//! Return this object as PDF number object.
const PdfNumber *PdfObj::number() const noexcept { return nullptr; }
//! Return this object as PDF string object.
const PdfString *PdfObj::string() const noexcept { return nullptr; }
//! Return this object as PDF name object.
const PdfName *PdfObj::name() const noexcept { return nullptr; }
//! Return this object as PDF reference object.
const PdfRef *PdfObj::ref() const noexcept { return nullptr; }
//! Return this object as PDF array object.
const PdfArray *PdfObj::array() const noexcept { return nullptr; }
//! Return this object as PDF dictionary object.
const PdfDict *PdfObj::dict() const noexcept { return nullptr; }
//! Return PDF representation of the object.
String PdfObj::repr() const noexcept
{
String d;
StringStream ss(d);
write(ss);
return d;
}
/*! \class ipe::PdfNull
* \ingroup base
* \brief The PDF null object.
*/
const PdfNull *PdfNull::null() const noexcept { return this; }
void PdfNull::write(Stream &stream, const PdfRenumber *, bool infl) const noexcept
{
stream << "null";
}
/*! \class ipe::PdfBool
* \ingroup base
* \brief The PDF bool object.
*/
const PdfBool *PdfBool::boolean() const noexcept { return this; }
void PdfBool::write(Stream &stream, const PdfRenumber *, bool infl) const noexcept
{
stream << (iValue ? "true" : "false");
}
/*! \class ipe::PdfNumber
* \ingroup base
* \brief The PDF number object.
*/
const PdfNumber *PdfNumber::number() const noexcept { return this; }
void PdfNumber::write(Stream &stream, const PdfRenumber *, bool infl) const noexcept
{
stream << iValue;
}
/*! \class ipe::PdfString
* \ingroup base
* \brief The PDF string object.
*/
const PdfString *PdfString::string() const noexcept { return this; }
void PdfString::write(Stream &stream, const PdfRenumber *, bool infl)
const noexcept
{
if (iBinary) {
stream << "<" << iValue << ">";
} else {
char octbuf[5];
stream << "(";
for (int i = 0; i < iValue.size(); ++i) {
int ch = iValue[i];
if ((0 <= ch && ch < 0x20) || ch == '\\' || ch == '(' || ch == ')') {
sprintf(octbuf, "\\%.3o", (ch & 0xff));
stream << octbuf;
} else
stream.putChar(ch);
}
stream << ")";
}
}
//! Return value of string after decoding binary strings.
String PdfString::decode() const noexcept
{
if (!iBinary)
return iValue;
String result;
Lex lex(iValue);
while (!lex.eos())
result += char(lex.getHexByte());
return result;
}
/*! \class ipe::PdfName
* \ingroup base
* \brief The PDF name object.
*/
const PdfName *PdfName::name() const noexcept { return this; }
void PdfName::write(Stream &stream, const PdfRenumber *, bool infl) const noexcept
{
stream << "/" << iValue;
}
/*! \class ipe::PdfRef
* \ingroup base
* \brief The PDF reference object (indirect object).
*/
const PdfRef *PdfRef::ref() const noexcept { return this; }
void PdfRef::write(Stream &stream, const PdfRenumber *renumber,
bool infl) const noexcept
{
if (renumber) {
auto it = renumber->find(iValue);
if (it != renumber->end()) {
stream << it->second << " 0 R";
return;
}
}
stream << iValue << " 0 R";
}
/*! \class ipe::PdfArray
* \ingroup base
* \brief The PDF array object.
*/
const PdfArray *PdfArray::array() const noexcept { return this; }
void PdfArray::write(Stream &stream, const PdfRenumber *renumber,
bool infl) const noexcept
{
stream << "[";
String sep = "";
for (int i = 0; i < count(); ++i) {
stream << sep;
sep = " ";
obj(i, nullptr)->write(stream, renumber);
}
stream << "]";
}
PdfArray::~PdfArray()
{
for (std::vector::iterator it = iObjects.begin();
it != iObjects.end(); ++it) {
delete *it;
*it = nullptr;
}
}
//! Append an object to array.
/*! Array takes ownership of the object. */
void PdfArray::append(const PdfObj *obj)
{
iObjects.push_back(obj);
}
//! Return object with \a index in array.
/*! Indirect objects (references) are looked up if \a file is not
nullptr, and the object referred to is returned (nullptr if it does not
exist). Object remains owned by array.
*/
const PdfObj *PdfArray::obj(int index, const PdfFile *file) const noexcept
{
const PdfObj *obj = iObjects[index];
if (file && obj->ref()) {
int n = obj->ref()->value();
return file->object(n);
}
return obj;
}
/*! \class ipe::PdfDict
* \ingroup base
* \brief The PDF dictionary and stream objects.
A dictionary may or may not have attached stream data.
*/
const PdfDict *PdfDict::dict() const noexcept { return this; }
//! Return PDF representation of the PdfDict without the stream.
String PdfDict::dictRepr() const noexcept
{
String d;
StringStream ss(d);
dictWrite(ss, nullptr, false, iStream.size());
return d;
}
void PdfDict::dictWrite(Stream &stream,
const PdfRenumber *renumber,
bool infl, int length) const noexcept
{
stream << "<<";
for (std::vector
- ::const_iterator it = iItems.begin();
it != iItems.end(); ++it) {
if (it != iItems.begin())
stream << " ";
if (infl && it->iKey == "Filter" && it->iVal->name()
&& it->iVal->name()->value() == "FlateDecode")
continue; // remove filter and inflate stream
stream << "/" << it->iKey << " ";
if (it->iKey == "Length")
stream << length;
else
it->iVal->write(stream, renumber);
}
stream << ">>";
}
void PdfDict::write(Stream &stream, const PdfRenumber *renumber,
bool infl) const noexcept
{
Buffer s = infl ? inflate() : iStream;
dictWrite(stream, renumber, infl, s.size());
if (s.size() > 0) {
stream << "\nstream\n";
for (int i = 0; i < s.size(); ++i)
stream.putChar(s[i]);
stream << "\nendstream";
}
}
PdfDict::~PdfDict()
{
for (std::vector
- ::iterator it = iItems.begin();
it != iItems.end(); ++it) {
delete it->iVal;
it->iVal = nullptr;
}
}
//! Add stream data to this dictionary.
void PdfDict::setStream(const Buffer &stream)
{
iStream = stream;
}
//! Add a (key, value) pair to the dictionary.
/*! Dictionary takes ownership of \a obj. */
void PdfDict::add(String key, const PdfObj *obj)
{
Item item;
item.iKey = key;
item.iVal = obj;
iItems.push_back(item);
}
//! Look up key in dictionary.
/*! Indirect objects (references) are looked up if \a file is not nullptr,
and the object referred to is returned.
Returns nullptr if key is not in dictionary.
*/
const PdfObj *PdfDict::get(String key, const PdfFile *file) const noexcept
{
for (std::vector
- ::const_iterator it = iItems.begin();
it != iItems.end(); ++it) {
if (it->iKey == key) {
if (file && it->iVal->ref())
return file->object(it->iVal->ref()->value());
else
return it->iVal;
}
}
return nullptr; // not in dictionary
}
//! Retrieve a single number and stor in \a val.
bool PdfDict::getNumber(String key, double &val, const PdfFile *file)
const noexcept
{
const PdfObj *obj = get(key, file);
if (!obj || !obj->number())
return false;
val = obj->number()->value();
return true;
}
//! Retrieve an array of numbers and store in \a vals.
bool PdfDict::getNumberArray(String key, const PdfFile *file,
std::vector &vals) const noexcept
{
const PdfObj *obj = get(key, file);
if (!obj || !obj->array())
return false;
vals.clear();
for (int i = 0; i < obj->array()->count(); i++) {
const PdfObj *a = obj->array()->obj(i, file);
if (!a || !a->number())
return false;
vals.push_back(a->number()->value());
}
return true;
}
//! Is this stream compressed with flate compression?
bool PdfDict::deflated() const noexcept
{
const PdfObj *f = get("Filter", nullptr);
return !(!f || !f->name() || f->name()->value() != "FlateDecode");
}
//! Return the (uncompressed) stream data.
/*! This only handles the /Flate compression. */
Buffer PdfDict::inflate() const noexcept
{
if (iStream.size() == 0 || !deflated())
return iStream;
String dest;
BufferSource bsource(iStream);
InflateSource source(bsource);
int ch = source.getChar();
while (ch != EOF) {
dest += char(ch);
ch = source.getChar();
}
return Buffer(dest.data(), dest.size());
}
// --------------------------------------------------------------------
/*! \class ipe::PdfParser
* \ingroup base
* \brief PDF parser
The parser understands the syntax of PDF files, but very little of
its semantics. It is meant to be able to parse PDF documents created
by Ipe for loading, and to extract information from PDF files created
by Pdflatex or Xelatex.
The parser reads a PDF file sequentially from front to back, ignores
the contents of 'xref' sections, stores only generation 0 objects,
and stops after reading the first 'trailer' section (so it cannot
deal with files with incremental updates). It cannot handle stream
objects whose /Length entry has been deferred (using an indirect
object).
*/
//! Construct with a data source.
PdfParser::PdfParser(DataSource &source)
: iSource(source)
{
iPos = 0;
getChar(); // init iCh
getToken(); // init iTok
}
//! Skip white space and comments.
void PdfParser::skipWhiteSpace()
{
while (!eos() && (specialChars[iCh] == 1 || iCh == '%')) {
// handle comment
if (iCh == '%') {
while (!eos() && iCh != '\n' && iCh != '\r')
getChar();
}
getChar();
}
}
//! Read the next token from the input stream.
void PdfParser::getToken()
{
iTok.iString.erase();
iTok.iType = PdfToken::EErr;
skipWhiteSpace();
if (eos())
return; // Err
// parse string
if (iCh == '(') {
int nest = 0;
getChar();
while (iCh != ')' || nest > 0) {
if (eos())
return; // Err
if (iCh == '\\') {
getChar();
if ('0' <= iCh && iCh <= '9') {
// octal char code
char buf[4];
int i = 0;
buf[i++] = char(iCh);
getChar();
if ('0' <= iCh && iCh <= '9') {
buf[i++] = char(iCh);
getChar();
}
if ('0' <= iCh && iCh <= '9') {
buf[i++] = char(iCh);
getChar();
}
buf[i] = '\0';
iTok.iString.append(char(std::strtol(buf, nullptr, 8)));
} else {
iTok.iString.append(char(iCh));
getChar();
}
} else {
if (iCh == '(')
++nest;
else if (iCh == ')')
--nest;
iTok.iString.append(char(iCh));
getChar();
}
}
getChar(); // skip closing ')'
iTok.iType = PdfToken::EString;
return;
}
if (iCh == '<') {
getChar();
// recognize dictionary separator "<<"
if (iCh == '<') {
getChar();
iTok.iType = PdfToken::EDictBg;
return;
}
// otherwise it's a binary string
while (iCh != '>') {
if (eos())
return; // Err
iTok.iString.append(char(iCh));
getChar();
}
// We don't bother to decode it
getChar(); // skip '>'
iTok.iType = PdfToken::EStringBinary;
return;
}
int ch = iCh;
iTok.iString.append(char(iCh));
getChar();
// recognize array separators
if (ch == '[') {
iTok.iType = PdfToken::EArrayBg;
return;
} else if (ch == ']') {
iTok.iType = PdfToken::EArrayEnd;
return;
}
// recognize dictionary separator ">>"
if (ch == '>') {
if (iCh != '>')
return; // Err
getChar();
iTok.iType = PdfToken::EDictEnd;
return;
}
// collect all characters up to white-space or separator
while (!specialChars[iCh]) {
if (eos())
return; // Err
iTok.iString.append(char(iCh));
getChar();
}
if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == '.')
iTok.iType = PdfToken::ENumber;
else if (ch == '/')
iTok.iType = PdfToken::EName;
else if (iTok.iString == "null")
iTok.iType = PdfToken::ENull;
else if (iTok.iString == "true")
iTok.iType = PdfToken::ETrue;
else if (iTok.iString == "false")
iTok.iType = PdfToken::EFalse;
else
iTok.iType = PdfToken::EOp;
}
// --------------------------------------------------------------------
//! Parse elements of an array.
PdfArray *PdfParser::makeArray()
{
std::unique_ptr arr(new PdfArray);
for (;;) {
if (iTok.iType == PdfToken::EArrayEnd) {
// finish array
getToken();
return arr.release();
}
// check for reference object
if (iTok.iType == PdfToken::ENumber) {
PdfToken t1 = iTok;
getToken();
if (iTok.iType == PdfToken::ENumber) {
PdfToken t2 = iTok;
getToken();
if (iTok.iType == PdfToken::EOp && iTok.iString == "R") {
arr->append(new PdfRef(toInt(t1.iString)));
getToken();
} else {
arr->append(new PdfNumber(Platform::toDouble(t1.iString)));
arr->append(new PdfNumber(Platform::toDouble(t2.iString)));
}
} else {
arr->append(new PdfNumber(Platform::toDouble(t1.iString)));
}
} else {
PdfObj *obj = getObject();
if (!obj)
return nullptr;
arr->append(obj);
}
}
}
PdfDict *PdfParser::makeDict()
{
std::unique_ptr dict(new PdfDict);
for (;;) {
if (iTok.iType == PdfToken::EDictEnd) {
// finish
getToken();
// check whether stream follows
if (iTok.iType != PdfToken::EOp || iTok.iString != "stream")
return dict.release();
// time to read the stream
while (!eos() && iCh != '\n')
getChar();
getChar(); // skip '\n'
// now at beginning of stream
const PdfObj *len = dict->get("Length", nullptr);
if (len && len->ref()) {
ipeDebug("/Length entry of dictionary is a reference.");
return nullptr;
}
if (!len || !len->number())
return nullptr;
int bytes = int(len->number()->value());
Buffer buf(bytes);
char *p = buf.data();
while (bytes--) {
*p++ = char(iCh);
getChar();
}
dict->setStream(buf);
getToken();
if (iTok.iType != PdfToken::EOp || iTok.iString != "endstream")
return nullptr;
getToken();
return dict.release();
}
// must read name
if (iTok.iType != PdfToken::EName)
return nullptr;
String name = iTok.iString.substr(1);
getToken();
// check for reference object
if (iTok.iType == PdfToken::ENumber) {
PdfToken t1 = iTok;
getToken();
if (iTok.iType == PdfToken::ENumber) {
PdfToken t2 = iTok;
getToken();
if (iTok.iType == PdfToken::EOp && iTok.iString == "R") {
dict->add(name, new PdfRef(toInt(t1.iString)));
getToken();
} else
return nullptr; // should be name or '>>'
} else
dict->add(name, new PdfNumber(Platform::toDouble(t1.iString)));
} else {
PdfObj *obj = getObject();
if (!obj)
return nullptr;
dict->add(name, obj);
}
}
}
//! Read one object from input stream.
PdfObj *PdfParser::getObject()
{
PdfToken tok = iTok;
getToken();
switch (tok.iType) {
case PdfToken::ENumber:
return new PdfNumber(Platform::toDouble(tok.iString));
case PdfToken::EString:
return new PdfString(tok.iString);
case PdfToken::EStringBinary:
return new PdfString(tok.iString, true);
case PdfToken::EName:
return new PdfName(tok.iString.substr(1));
case PdfToken::ENull:
return new PdfNull;
case PdfToken::ETrue:
return new PdfBool(true);
case PdfToken::EFalse:
return new PdfBool(false);
case PdfToken::EArrayBg:
return makeArray();
case PdfToken::EDictBg:
return makeDict();
// anything else is an error
case PdfToken::EErr:
default:
return nullptr;
}
}
//! Parse an object definition (current token is object number).
PdfObj *PdfParser::getObjectDef()
{
getToken();
if (iTok.iType != PdfToken::ENumber || iTok.iString != "0")
return nullptr;
getToken();
if (iTok.iType != PdfToken::EOp || iTok.iString != "obj")
return nullptr;
getToken();
PdfObj *obj = getObject();
if (!obj)
return nullptr;
if (iTok.iType != PdfToken::EOp || iTok.iString != "endobj")
return nullptr;
getToken();
return obj;
}
//! Skip xref table (current token is 'xref')
void PdfParser::skipXRef()
{
getToken(); // first object number
getToken(); // number of objects
int k = toInt(iTok.iString);
getToken();
while (k--) {
getToken(); // obj num
getToken(); // gen num
getToken(); // n or f
}
}
//! Parse trailer dictionary (current token is 'trailer')
PdfDict *PdfParser::getTrailer()
{
getToken();
if (iTok.iType != PdfToken::EDictBg)
return nullptr;
getToken();
return makeDict();
}
// --------------------------------------------------------------------
/*! \class ipe::PdfFile
* \ingroup base
* \brief All information obtained by parsing a PDF file.
*/
bool PdfFile::parseObjectStream(const PdfDict *d)
{
const PdfObj *objn = d->get("N", this);
const PdfObj *objfirst = d->get("First", this);
int n = objn->number() ? objn->number()->value() : -1;
int first = objfirst->number() ? objfirst->number()->value() : -1;
if (n < 0 || first < 0)
return false;
Buffer stream = d->inflate();
BufferSource source(stream);
PdfParser parser(source);
std::vector dir;
for (int i = 0; i < 2 * n; ++i) {
PdfToken t = parser.token();
if (t.iType != PdfToken::ENumber)
return false;
dir.push_back(toInt(t.iString));
parser.getToken();
}
for (int i = 0; i < n; ++i) {
int num = dir[2*i];
source.setPosition(first + dir[2*i+1]);
parser.getChar();
parser.getToken();
PdfObj *obj = parser.getObject();
if (!obj)
return false;
// ipeDebug("Object: %s", obj->repr().z());
iObjects[num] = std::unique_ptr(obj);
}
return true;
}
//! Parse entire PDF stream, and store objects.
bool PdfFile::parse(DataSource &source)
{
PdfParser parser(source);
for (;;) {
PdfToken t = parser.token();
if (t.iType == PdfToken::ENumber) {
// 0 obj starts an object
int num = toInt(t.iString);
std::unique_ptr obj(parser.getObjectDef());
if (!obj) {
ipeDebug("Failed to get object %d", num);
return false;
}
const PdfDict *d = obj->dict();
const PdfObj *type = d ? d->get("Type", this) : nullptr;
if (type && type->name() && type->name()->value() == "ObjStm") {
if (!parseObjectStream(d))
return false;
} else if (type && type->name() && type->name()->value() == "XRef") {
iTrailer = std::unique_ptr(obj.release()->dict());
} else {
// ipeDebug("Object: %s", obj->repr().z());
iObjects[num] = std::move(obj);
}
} else if (t.iType == PdfToken::EOp) {
if (t.iString == "trailer") {
iTrailer = std::unique_ptr(parser.getTrailer());
if (!iTrailer) {
ipeDebug("Failed to get trailer");
return false;
}
return readPageTree();
} else if (t.iString == "xref") {
parser.skipXRef();
} else if (t.iString == "startxref") {
return readPageTree(); // this is the end
} else {
ipeDebug("Weird token: %s", t.iString.z());
// don't know what's happening
return false;
}
} else {
ipeDebug("Weird token type: %d %s", t.iType, t.iString.z());
// don't know what's happening
return false;
}
}
}
//! Return object with number \a num.
const PdfObj *PdfFile::object(int num) const noexcept
{
auto got = iObjects.find(num);
if (got != iObjects.end())
return got->second.get();
else
return nullptr;
}
//! Take ownership of object with number \a num, remove from PdfFile.
std::unique_ptr PdfFile::take(int num)
{
auto got = iObjects.find(num);
if (got != iObjects.end()) {
std::unique_ptr obj = std::move(got->second);
iObjects.erase(got);
return obj;
} else
return std::unique_ptr();
}
//! Return root catalog of PDF file.
const PdfDict *PdfFile::catalog() const noexcept
{
const PdfObj *root = iTrailer->get("Root", this);
assert(root && root->dict());
return root->dict();
}
bool PdfFile::readPageTree(const PdfObj *ptn)
{
if (ptn == nullptr)
ptn = catalog()->get("Pages", this);
if (!ptn || !ptn->dict())
return false;
const PdfObj *kids = ptn->dict()->get("Kids", this);
if (!kids || !kids->array())
return false;
for (int i = 0; i < kids->array()->count(); ++i) {
const PdfObj *page = kids->array()->obj(i, this);
if (!page || !page->dict())
return false;
const PdfObj *type = page->dict()->get("Type", this);
if (!type || !type->name())
return false;
if (type->name()->value() == "Pages")
readPageTree(page);
else if (type->name()->value() == "Page")
iPages.push_back(page->dict());
else
return false;
}
return true;
}
//! Return a page of the document.
const PdfDict *PdfFile::page(int pno) const noexcept
{
if (pno < 0 || pno >= countPages())
return nullptr;
return iPages[pno];
}
//! Return mediabox of a page.
Rect PdfFile::mediaBox(const PdfDict *pg) const
{
Rect box;
std::vector a;
if (pg && pg->getNumberArray("MediaBox", this, a) && a.size() == 4) {
box.addPoint(Vector(a[0], a[1]));
box.addPoint(Vector(a[2], a[3]));
}
return box;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipebase.cpp 0000644 0001750 0001750 00000046407 13561570220 016416 0 ustar otfried otfried // --------------------------------------------------------------------
// Basic classes
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipebase.h"
#include
#include
#include
using namespace ipe;
// --------------------------------------------------------------------
/*! \defgroup base Ipe Base
\brief Basic classes for Ipe.
Some very basic type definitions, streams, lexical analysis, and XML
parsing.
All parts of Ipe make use of the STL. The C++ I/O streams library
is not used, as Ipelib doesn't do much I/O. Ipe objects support
internalization and externalization through an abstract interface
based on ipe::Stream's.
Clients of Ipelib can use any I/O library that implements this
interface. Ipe simply uses \c cstdio.
*/
// --------------------------------------------------------------------
/*! \class ipe::String
\ingroup base
\brief Strings and buffers.
String is is an implicitly shared byte string. It is designed to
be efficient for strings of arbitrary length, and supposed to be
passed by value (the size of String is a single pointer).
Sharing is implicit---the string creates its own representation as
soon as it is modified.
String can be used for binary data. For text, it is usually
assumed that the string is UTF-8 encoded, but only the unicode
member function actually requires this. In particular, all indices
into the string are byte indices, not Unicode character indices.
*/
String::Imp *String::theEmptyString = { nullptr };
String::Imp *String::emptyString() noexcept
{
if (theEmptyString == nullptr) {
theEmptyString = new Imp;
theEmptyString->iRefCount = 10; // always > 1
theEmptyString->iSize = 0;
theEmptyString->iCapacity = 0;
theEmptyString->iData = nullptr;
}
// increment every time it's requested to make sure it's never destroyed
++theEmptyString->iRefCount;
return theEmptyString;
}
//! Construct an empty string.
String::String() noexcept
{
iImp = emptyString();
}
//! Construct a string by making copy of \a str.
String::String(const char *str) noexcept
{
if (!str || !str[0]) {
iImp = emptyString();
} else {
int len = strlen(str);
iImp = new Imp;
iImp->iRefCount = 1;
iImp->iSize = len;
iImp->iCapacity = (len + 32) & ~15;
iImp->iData = new char[iImp->iCapacity];
memcpy(iImp->iData, str, iImp->iSize);
}
}
//! Construct string by taking ownership of given \a data.
String String::withData(char *data, int len) noexcept
{
if (!len)
len = strlen(data);
String r; // empty string
--r.iImp->iRefCount;
r.iImp = new Imp;
r.iImp->iRefCount = 1;
r.iImp->iSize = len;
r.iImp->iCapacity = len;
r.iImp->iData = data;
return r;
}
//! Construct string by making copy of \a str with given \a len.
String::String(const char *str, int len) noexcept
{
if (!str || !len)
iImp = emptyString();
else {
iImp = new Imp;
iImp->iRefCount = 1;
iImp->iSize = len;
iImp->iCapacity = (len + 32) & ~15;
iImp->iData = new char[iImp->iCapacity];
memcpy(iImp->iData, str, iImp->iSize);
}
}
//! Copy constructor.
//! This only copies the reference and takes constant time.
String::String(const String &rhs) noexcept
{
iImp = rhs.iImp;
iImp->iRefCount++;
}
//! Construct a substring.
/*! \a index must be >= 0. \a len can be negative or too large to
return entire string. */
String::String(const String &rhs, int index, int len) noexcept
{
// actually available data
int len1 = index < rhs.size() ? rhs.size() - index : 0;
if (len < 0 || len1 < len)
len = len1;
if (!len) {
iImp = emptyString();
} else {
iImp = new Imp;
iImp->iRefCount = 1;
iImp->iSize = len;
iImp->iCapacity = (len + 32) & ~15;
iImp->iData = new char[iImp->iCapacity];
memcpy(iImp->iData, rhs.iImp->iData + index, len);
}
}
//! Assignment takes constant time.
String &String::operator=(const String &rhs) noexcept
{
if (iImp != rhs.iImp) {
if (iImp->iRefCount == 1) {
delete [] iImp->iData;
delete iImp;
} else
iImp->iRefCount--;
iImp = rhs.iImp;
iImp->iRefCount++;
}
return *this;
}
//! Destruct string if reference count has reached zero.
String::~String() noexcept
{
if (iImp->iRefCount == 1) {
delete [] iImp->iData;
delete iImp;
} else
iImp->iRefCount--;
}
//! Make a private copy of the string with \a n bytes to spare.
/*! When a private copy has to be made an extra 32 bytes are ensured. */
void String::detach(int n) noexcept
{
if (iImp == theEmptyString) {
iImp = new Imp;
iImp->iRefCount = 1;
iImp->iSize = 0;
n = (n + 0x1f) & ~0x1f;
iImp->iCapacity = (n >= 16) ? n : 16;
iImp->iData = new char[iImp->iCapacity];
} else if (iImp->iRefCount > 1 || (iImp->iSize + n > iImp->iCapacity)) {
Imp *imp = new Imp;
imp->iRefCount = 1;
imp->iSize = iImp->iSize;
imp->iCapacity = iImp->iCapacity;
while (imp->iSize + 32 + n > imp->iCapacity)
imp->iCapacity *= 2;
imp->iData = new char[imp->iCapacity];
memcpy(imp->iData, iImp->iData, imp->iSize);
iImp->iRefCount--;
if (iImp->iRefCount == 0) {
delete [] iImp->iData;
delete iImp;
}
iImp = imp;
}
}
//! Return a C style string with final zero byte.
const char *String::z() const noexcept
{
if (iImp == theEmptyString)
return "";
String *This = const_cast(this);
if (iImp->iSize == iImp->iCapacity)
This->detach(1);
This->iImp->iData[iImp->iSize] = '\0';
return data();
}
//! Return index of first occurrence of ch.
/*! Return -1 if character does not appear. */
int String::find(char ch) const noexcept
{
for (int i = 0; i < size(); ++i)
if (iImp->iData[i] == ch)
return i;
return -1;
}
//! Return index of first occurrence of rhs.
/*! Return -1 if not substring is not present. */
int String::find(const char *rhs) const noexcept
{
int s = strlen(rhs);
for (int i = 0; i < size() - s; ++i)
if (::strncmp(iImp->iData + i, rhs, s) == 0)
return i;
return -1;
}
//! Return line starting at position \a index.
//! Index is updated to point to next line.
String String::getLine(int &index) const noexcept
{
int i = index;
while (i < size() && iImp->iData[i] != '\r' && iImp->iData[i] != '\n')
++i;
String result = substr(index, i-index);
if (i < size() && iImp->iData[i] == '\r')
++i;
if (i < size() && iImp->iData[i] == '\n')
++i;
index = i;
return result;
}
//! Return index of last occurrence of ch.
/*! Return -1 if character does not appear. */
int String::rfind(char ch) const noexcept
{
for (int i = size() - 1; i >= 0; --i)
if (iImp->iData[i] == ch)
return i;
return -1;
}
//! Make string empty.
void String::erase() noexcept
{
detach(0);
iImp->iSize = 0;
}
//! Append \a rhs to this string.
void String::append(const String &rhs) noexcept
{
int n = rhs.size();
detach(n);
memcpy(iImp->iData + iImp->iSize, rhs.iImp->iData, n);
iImp->iSize += n;
}
//! Append \a rhs to this string.
void String::append(const char *rhs) noexcept
{
int n = strlen(rhs);
if (n) {
detach(n);
memcpy(iImp->iData + iImp->iSize, rhs, n);
iImp->iSize += n;
}
}
//! Append \a ch to this string.
void String::append(char ch) noexcept
{
detach(1);
iImp->iData[iImp->iSize++] = ch;
}
//! Create substring at the right.
/*! Returns the entire string if \a i is larger than its length. */
String String::right(int i) const noexcept
{
if (i < size())
return String(*this, size() - i, i);
else
return *this;
}
//! Does string start with this prefix? (bytewise comparison)
bool String::hasPrefix(const char *rhs) const noexcept
{
int n = strlen(rhs);
return (size() >= n && !strncmp(iImp->iData, rhs, n));
}
//! Equality operator (bytewise comparison).
bool String::operator==(const String &rhs) const noexcept
{
return (size() == rhs.size() &&
!strncmp(iImp->iData, rhs.iImp->iData, size()));
}
//! Equality operator (bytewise comparison).
bool String::operator==(const char *rhs) const noexcept
{
int n = strlen(rhs);
return (size() == n && !strncmp(iImp->iData, rhs, n));
}
//! Inequality operator (bytewise comparison).
bool String::operator<(const String &rhs) const noexcept
{
int n = size() < rhs.size() ? size() : rhs.size();
int cmp = ::strncmp(iImp->iData, rhs.iImp->iData, n);
return (cmp < 0 || (cmp == 0 && size() < rhs.size()));
}
//! Concatenate this string with \a rhs.
String String::operator+(const String &rhs) const noexcept
{
String s(*this);
s.append(rhs);
return s;
}
static const uint8_t bytesFromUTF8[256] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
};
static const uint8_t firstByteMark[7] = {
0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0
};
//! Return Unicode value from UTF-8 string.
/*! The \a index is incremented to the next UTF-8 character.
This returns 0xfffd if there is any problem in parsing UTF-8. */
int String::unicode(int &index) const noexcept
{
int wch = uint8_t(iImp->iData[index++]);
if ((wch & 0xc0) == 0x80) {
// not on first byte of a UTF-8 sequence
while (index < iImp->iSize && (iImp->iData[index] & 0xc0) == 0x80)
index++;
return 0xfffd;
}
int extraBytes = bytesFromUTF8[wch & 0xff];
wch -= firstByteMark[extraBytes];
while (extraBytes--) {
if (index >= iImp->iSize)
return 0xfffd; // UTF-8 sequence is incomplete
if ((iImp->iData[index] & 0xc0) != 0x80)
return 0xfffd; // UTF-8 sequence is incorrect
wch <<= 6;
wch |= (iImp->iData[index++] & 0x3f);
}
return wch;
}
// --------------------------------------------------------------------
/*! \class ipe::Fixed
\ingroup base
\brief Fixed point number with three (decimal) fractional digits
*/
//! Return value times (a/b)
Fixed Fixed::mult(int a, int b) const
{
return Fixed::fromInternal(iValue * a / b);
}
Fixed Fixed::fromDouble(double val)
{
return Fixed::fromInternal(int(val * 1000 + 0.5));
}
namespace ipe {
/*! \relates Fixed */
Stream &operator<<(Stream &stream, const Fixed &f)
{
stream << (f.iValue / 1000);
if (f.iValue % 1000) {
stream << "." << ((f.iValue / 100) % 10);
if (f.iValue % 100) {
stream << ((f.iValue / 10) % 10);
if (f.iValue % 10)
stream << (f.iValue % 10);
}
}
return stream;
}
}
// --------------------------------------------------------------------
/*! \class ipe::Lex
\ingroup base
\brief Lexical analyser. Seeded with a string.
*/
//! Construct lexical analyzer from a string.
Lex::Lex(String str) : iString(str), iPos(0)
{
// nothing
}
//! Return NextToken, but without extracting it.
String Lex::token()
{
int pos = iPos;
String str = nextToken();
iPos = pos;
return str;
}
//! Extract next token.
/*! Skips any whitespace before the token.
Returns empty string if end of string is reached. */
String Lex::nextToken()
{
skipWhitespace();
int mark = iPos;
while (!(eos() || uint8_t(iString[iPos]) <= ' '))
++iPos;
return iString.substr(mark, iPos - mark);
}
//! Extract integer token (skipping whitespace).
int Lex::getInt()
{
String str = nextToken();
return std::strtol(str.z(), nullptr, 10);
}
inline int hexDigit(int ch)
{
if ('0' <= ch && ch <= '9')
return ch - '0';
if ('a' <= ch && ch <= 'f')
return ch - 'a' + 10;
if ('A' <= ch && ch <= 'F')
return ch - 'A' + 10;
return 0;
}
//! Extract byte in hex (skipping whitespace).
int Lex::getHexByte()
{
int ch1 = '0', ch2 = '0';
skipWhitespace();
if (!eos())
ch1 = iString[iPos++];
skipWhitespace();
if (!eos())
ch2 = iString[iPos++];
return (hexDigit(ch1) << 4) | hexDigit(ch2);
}
//! Extract hexadecimal token (skipping whitespace).
unsigned long int Lex::getHexNumber()
{
String str = nextToken();
return std::strtoul(str.z(), nullptr, 16);
}
//! Extract Fixed token (skipping whitespace).
Fixed Lex::getFixed()
{
String str = nextToken();
int i = 0;
while (i < str.size() && str[i] != '.')
++i;
int integral = std::strtol(str.substr(0, i).z(), nullptr, 10);
int fractional = 0;
if (i < str.size()) {
String s = (str.substr(i+1) + "000").substr(0, 3);
fractional = std::strtol(s.z(), nullptr, 10);
}
return Fixed::fromInternal(integral * 1000 + fractional);
}
//! Extract double token (skipping whitespace).
double Lex::getDouble()
{
return Platform::toDouble(nextToken());
}
//! Skip over whitespace.
void Lex::skipWhitespace()
{
while (!eos() && uint8_t(iString[iPos]) <= ' ')
++iPos;
}
// --------------------------------------------------------------------
/*! \class ipe::Buffer
\ingroup base
\brief A memory buffer.
Can be be copied in constant time, the actual data is shared.
*/
//! Create buffer of specified size.
Buffer::Buffer(int size)
{
iData = std::shared_ptr>(new std::vector(size));
}
//! Create buffer by copying the data.
Buffer::Buffer(const char *data, int size)
{
iData = std::shared_ptr>(new std::vector(size));
std::memcpy(&(*iData)[0], data, size);
}
// --------------------------------------------------------------------
/*! \class ipe::Stream
\ingroup base
\brief Abstract base class for output streams.
*/
Stream::~Stream()
{
// empty implementation of pure virtual destructor
}
//! Close the stream. No more writing allowed!
void Stream::close()
{
// nothing
}
//! Default implementation uses PutChar.
void Stream::putString(String s)
{
for (int i = 0; i < s.size(); ++i)
putChar(s[i]);
}
//! Default implementation uses PutChar.
void Stream::putCString(const char *s)
{
while (*s)
putChar(*s++);
}
//! Default implementation uses PutChar.
void Stream::putRaw(const char *data, int size)
{
for (int i = 0; i < size; i++)
putChar(data[i]);
}
//! Output integer.
Stream &Stream::operator<<(int i)
{
char buf[30];
std::sprintf(buf, "%d", i);
*this << buf;
return *this;
}
//! Output double.
Stream &Stream::operator<<(double d)
{
char buf[30];
if (d < 0.0) {
putChar('-');
d = -d;
}
if (d >= 1e9) {
// PDF will not be able to read this, but we have to write something.
// Such large numbers should only happen if something is wrong.
std::sprintf(buf, "%g", d);
putCString(buf);
} else if (d < 1e-8) {
putChar('0');
} else {
// Print six significant digits, but omit trailing zeros.
// Probably I'll want to have adjustable precision later.
int factor;
if (d > 1000.0)
factor = 100L;
else if (d > 100.0)
factor = 1000L;
else if (d > 10.0)
factor = 10000L;
else if (d > 1.0)
factor = 100000L;
else if (d > 0.1)
factor = 1000000L;
else if (d > 0.01)
factor = 10000000L;
else
factor = 100000000L;
double dd = trunc(d);
int intpart = int(dd + 0.5);
// 10^9 < 2^31
int v = int(factor * (d - dd) + 0.5);
if (v >= factor) {
++intpart;
v -= factor;
}
std::sprintf(buf, "%d", intpart);
putCString(buf);
int mask = factor / 10;
if (v != 0) {
putChar('.');
while (v != 0) {
putChar('0' + v / mask);
v = (10 * v) % factor;
}
}
}
return *this;
}
//! Output byte in hexadecimal.
void Stream::putHexByte(char b)
{
char buf[3];
std::sprintf(buf, "%02x", (b & 0xff));
*this << buf;
}
//! Save a string with XML escaping of &, >, <, ", '.
void Stream::putXmlString(String s)
{
for (int i = 0; i < s.size(); ++i) {
char ch = s[i];
switch (ch) {
case '&': *this << "&"; break;
case '<': *this << "<"; break;
case '>': *this << ">"; break;
case '"': *this << """; break;
case '\'': *this << "'"; break;
default:
*this << ch;
break;
}
}
}
// --------------------------------------------------------------------
/*! \class ipe::StringStream
\ingroup base
\brief Stream writing into an String.
*/
//! Construct with string reference.
StringStream::StringStream(String &string)
: iString(string)
{
// nothing
}
void StringStream::putChar(char ch)
{
iString += ch;
}
void StringStream::putString(String s)
{
iString += s;
}
void StringStream::putCString(const char *s)
{
iString += s;
}
void StringStream::putRaw(const char *data, int size)
{
for (int i = 0; i < size; i++)
iString += data[i];
}
long StringStream::tell() const
{
return iString.size();
}
// --------------------------------------------------------------------
/*! \class ipe::FileStream
\ingroup base
\brief Stream writing into an open file.
*/
//! Constructor.
FileStream::FileStream(std::FILE *file)
: iFile(file)
{
// nothing
}
void FileStream::putChar(char ch)
{
std::fputc(ch, iFile);
}
void FileStream::putString(String s)
{
for (int i = 0; i < s.size(); ++i)
std::fputc(s[i], iFile);
}
void FileStream::putCString(const char *s)
{
fputs(s, iFile);
}
void FileStream::putRaw(const char *data, int size)
{
for (int i = 0; i < size; i++)
std::fputc(data[i], iFile);
}
long FileStream::tell() const
{
return std::ftell(iFile);
}
// --------------------------------------------------------------------
/*! \class ipe::DataSource
* \ingroup base
* \brief Interface for getting data for parsing.
*/
//! Pure virtual destructor.
DataSource::~DataSource()
{
// nothing
}
// --------------------------------------------------------------------
/*! \class ipe::FileSource
\ingroup base
\brief Data source for parsing from a file.
*/
FileSource::FileSource(FILE *file)
: iFile(file)
{
// nothing
}
int FileSource::getChar()
{
return std::fgetc(iFile);
}
/*! \class ipe::BufferSource
\ingroup base
\brief Data source for parsing from a buffer.
*/
BufferSource::BufferSource(const Buffer &buffer)
: iBuffer(buffer)
{
iPos = 0;
}
void BufferSource::setPosition(int pos)
{
iPos = pos;
}
int BufferSource::getChar()
{
if (iPos >= iBuffer.size())
return EOF;
return uint8_t(iBuffer[iPos++]);
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipexml.cpp 0000644 0001750 0001750 00000017011 13561570220 016271 0 ustar otfried otfried // --------------------------------------------------------------------
// XML parsing
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipexml.h"
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::XmlAttributes
\ingroup base
\brief Stores attributes of an XML tag.
*/
//! Constructor for an empty collection.
XmlAttributes::XmlAttributes()
{
iSlash = false;
}
//! Remove all attributes.
void XmlAttributes::clear()
{
iSlash = false;
iMap.clear();
}
//! Return attribute with given key.
/*! Returns an empty string if no attribute with this key exists. */
String XmlAttributes::operator[](String str) const
{
std::map::const_iterator it = iMap.find(str);
if (it == iMap.end())
return String();
else
return it->second;
}
//! Add a new attribute.
void XmlAttributes::add(String key, String val)
{
iMap[key] = val;
}
//! Check whether attribute exists, set \c val if so.
bool XmlAttributes::has(String str, String &val) const
{
std::map::const_iterator it = iMap.find(str);
if (it != iMap.end()) {
val = it->second;
return true;
}
return false;
}
//! Check whether attribute exists.
bool XmlAttributes::has(String str) const
{
return (iMap.find(str) != iMap.end());
}
// --------------------------------------------------------------------
/*! \class ipe::XmlParser
* \ingroup base
* \brief Base class for XML stream parsing.
This is the base class for Ipe's XML parser. It only provides some
utility functions for parsing tags and PCDATA. Derived classes
implement the actual parsing using recursive descent parsers---after
experimenting with various schemes for XML parsing, this seems to
work best for Ipe.
Tag names and attribute names must consist of ASCII letters only.
Only entities for '&', '<', and '>' are recognized.
*/
//! Construct with a data source.
XmlParser::XmlParser(DataSource &source)
: iSource(source)
{
iPos = 0;
getChar(); // init iCh
}
//! Virtual destructor, so one can destroy through pointer.
XmlParser::~XmlParser()
{
// nothing
}
void XmlParser::skipWhitespace()
{
while (iCh <= ' ' && !eos())
getChar();
}
//! Parse whitespace and the name of a tag.
/*! If the tag is a closing tag, skips > and returns with stream after that.
Otherwise, returns with stream just after the tag name.
Comments and are skipped silently.
*/
String XmlParser::parseToTagX()
{
bool comment_found;
do {
skipWhitespace();
if (iCh != '<')
return String();
getChar();
//
//
comment_found = (iCh == '!');
if (comment_found) {
getChar();
if (iCh == '-') {
int last[2] = { ' ', ' ' };
while (!eos() && (iCh != '>' || last[0] != '-' || last[1] != '-')) {
last[0] = last[1];
last[1] = iCh;
getChar();
}
} else {
// skip to end of tag
while (!eos() && iCh != '>')
getChar();
}
getChar();
if (eos())
return String();
}
} while (comment_found);
String tagname;
if (iCh == '?' || iCh == '/') {
tagname += char(iCh);
getChar();
}
while (isTagChar(iCh)) {
tagname += char(iCh);
getChar();
}
if (tagname[0] == '/') {
skipWhitespace();
if (iCh != '>')
return String();
getChar();
}
// ipeDebug("<%s>", tagname.z());
return tagname;
}
//! Parse whitespace and the name of a tag.
/*! Like ParseToTagX, but silently skips over all tags whose name
starts with "x-" */
String XmlParser::parseToTag()
{
for (;;) {
String s = parseToTagX();
if (s.size() < 3 ||
(s[0] != '/' && (s[0] != 'x' || s[1] != '-')) ||
(s[0] == '/' && (s[1] != 'x' || s[2] != '-')))
return s;
if (s[0] != '/') {
XmlAttributes attr;
if (!parseAttributes(attr))
return String();
}
}
}
static String fromXml(String source)
{
String s;
for (int i = 0; i < source.size(); ) {
if (source[i] == '&') {
int j = i;
while (j < source.size() && source[j] != ';')
++j;
String ent(source, i + 1, j - i - 1);
char ent1 = 0;
if (ent == "amp")
ent1 = '&';
else if (ent == "lt")
ent1 = '<';
else if (ent == "gt")
ent1 = '>';
else if (ent == "quot")
ent1 = '"';
else if (ent == "apos")
ent1 = '\'';
if (ent1) {
s += ent1;
i = j+1;
continue;
}
// entity not found: copy normally
}
s += source[i++];
}
return s;
}
//! Parse XML attributes.
/*! Returns with stream just after \>. Caller can check whether the
tag ended with a / by checking attr.slash().
Set \a qm to true to allow a question mark just before the \>.
*/
bool XmlParser::parseAttributes(XmlAttributes &attr, bool qm)
{
// looking at char after tagname
attr.clear();
skipWhitespace();
while (iCh != '>' && iCh != '/' && iCh != '?') {
String attname;
while (isTagChar(iCh)) {
attname += char(iCh);
getChar();
}
// XML allows whitespace before and after the '='
skipWhitespace();
if (attname.empty() || iCh != '=')
return false;
getChar();
skipWhitespace();
// XML allows double or single quotes
int quote = iCh;
if (iCh != '\"' && iCh != '\'')
return false;
getChar();
String val;
while (!eos() && iCh != quote) {
val += char(iCh);
getChar();
}
if (iCh != quote)
return false;
getChar();
skipWhitespace();
attr.add(attname, fromXml(val));
}
// looking at '/' or '>' (or '?' in tag)
if (iCh == '/' || (qm && iCh == '?')) {
attr.setSlash();
getChar();
skipWhitespace();
}
// looking at '>'
if (iCh != '>')
return false;
getChar();
return true;
}
//! Parse PCDATA.
/*! Checks whether the data is terminated by \c \, and returns
with stream past the \>. */
bool XmlParser::parsePCDATA(String tag, String &pcdata)
{
String s;
bool haveEntity = false;
for (;;) {
if (eos())
return false;
if (iCh == '<') {
getChar();
if (iCh != '/')
return false;
getChar();
for (int i = 0; i < tag.size(); i++) {
if (iCh != tag[i])
return false;
getChar();
}
skipWhitespace();
if (iCh != '>')
return false;
getChar();
if (haveEntity)
pcdata = fromXml(s);
else
pcdata = s;
return true;
} else {
if (iCh == '&')
haveEntity = true;
s += char(iCh);
}
getChar();
}
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/Makefile 0000644 0001750 0001750 00000003352 13561570220 015732 0 ustar otfried otfried # --------------------------------------------------------------------
# Makefile for Ipelib
# --------------------------------------------------------------------
OBJDIR = $(BUILDDIR)/obj/ipelib
include ../common.mak
TARGET = $(call dll_target,ipe)
MAKE_SYMLINKS = $(call dll_symlinks,ipe)
SONAME = $(call soname,ipe)
INSTALL_SYMLINKS = $(call install_symlinks,ipe)
CPPFLAGS += -I../include
ifdef IPEBUNDLE
CPPFLAGS += -DIPEBUNDLE
endif
ifdef IPE_NO_IPELIB_VERSION_CHECK
CPPFLAGS += -DIPE_NO_IPELIB_VERSION_CHECK
endif
CPPFLAGS += $(ZLIB_CFLAGS) $(JPEG_CFLAGS) $(PNG_CFLAGS)
CXXFLAGS += $(DLL_CFLAGS)
LIBS += $(JPEG_LIBS) $(ZLIB_LIBS) $(PNG_LIBS)
all: $(TARGET)
sources = \
ipebase.cpp \
ipeplatform.cpp \
ipegeo.cpp \
ipexml.cpp \
ipeattributes.cpp \
ipebitmap.cpp \
ipeshape.cpp \
ipegroup.cpp \
ipeimage.cpp \
ipetext.cpp \
ipepath.cpp \
ipereference.cpp \
ipeobject.cpp \
ipefactory.cpp \
ipestdstyles.cpp \
ipeiml.cpp \
ipepage.cpp \
ipepainter.cpp \
ipetoolbase.cpp \
ipepdfparser.cpp \
ipepdfwriter.cpp \
iperesources.cpp \
ipestyle.cpp \
ipesnap.cpp \
ipeutils.cpp \
ipelatex.cpp \
ipedoc.cpp
ifdef WIN32
sources += ipebitmap_win.cpp
else
sources += ipebitmap_unix.cpp
endif
$(TARGET): $(objects)
$(MAKE_LIBDIR)
$(CXX) $(LDFLAGS) $(DLL_LDFLAGS) $(SONAME) -o $@ $^ $(LIBS)
$(MAKE_SYMLINKS)
clean:
@-rm -f $(objects) $(TARGET) $(DEPEND)
$(DEPEND): Makefile
$(MAKE_DEPEND)
-include $(DEPEND)
install: $(TARGET)
$(INSTALL_DIR) $(INSTALL_ROOT)$(IPELIBDIR)
$(INSTALL_DIR) $(INSTALL_ROOT)$(IPEHEADERDIR)
$(INSTALL_PROGRAMS) $(TARGET) $(INSTALL_ROOT)$(IPELIBDIR)
$(INSTALL_FILES) ../include/*.h $(INSTALL_ROOT)$(IPEHEADERDIR)
$(INSTALL_SYMLINKS)
# --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipeshape.cpp 0000644 0001750 0001750 00000061604 13561570220 016600 0 ustar otfried otfried // --------------------------------------------------------------------
// Shapes
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipeshape.h"
#include "ipepainter.h"
using namespace ipe;
// --------------------------------------------------------------------
inline bool snapVertex(const Vector &mouse, const Vector &v,
Vector &pos, double &bound)
{
return v.snap(mouse, pos, bound);
}
inline void snapBezier(const Vector &mouse, const Bezier &bez,
Vector &pos, double &bound)
{
double t;
(void) bez.snap(mouse, t, pos, bound);
}
// --------------------------------------------------------------------
/*! \class ipe::CurveSegment
\ingroup geo
\brief A segment on an SubPath.
A segment is either an elliptic arc, a straight segment, or a
B-spline curve, depending on its type(). This is a lightweight
object, created on the fly by Curve::segment(). There is no public
constructor, so the only way to create such an object is through
that method.
The type() is one of the following:
- \c ESegment: the segment has two control points, and represents a
line segment.
- \c ESpline: a B-spline curve with n control points. The first and
last control point's knot value is repeated three times, so the
curve begins and ends in these points. A spline with 4 control
points is a single Bezier curve with those control points. A
spline with 3 control points is defined to be a quadratic Bezier
curve with those control points.
- \c EOldSpline: an incorrectly defined B-spline, used by Ipe for
many years. Supported for compatibility.
- \c EArc: an elliptic arc, with begin and end point. The
supporting ellipse is defined by the matrix(), it is the image
under the affine transformation matrix() of the unit circle.
matrix() is such that its inverse transforms both start and end
position to points (nearly) on the unit circle. The arc is the
image of the positively (counter-clockwise) directed arc from the
pre-image of the start position to the pre-image of the end
position. Whether this is a positively or negatively oriented arc
in user space depends on the matrix.
*/
//! Create a segment.
/*! Matrix \a m defaults to null, for all segments but arcs. */
CurveSegment::CurveSegment(Type type, int num, const Vector *cp,
const Matrix *m)
: iType(type), iCP(cp), iNumCP(num), iM(m)
{
// nothing
}
//! Return segment as Arc.
/*! Panics if segment is not an arc. */
Arc CurveSegment::arc() const
{
assert(type() == EArc);
return Arc(*iM, cp(0), cp(1));
}
//! Convert B-spline to a sequence of Bezier splines.
void CurveSegment::beziers(std::vector &bez) const
{
if (iType == EOldSpline)
Bezier::oldSpline(countCP(), iCP, bez);
else
Bezier::spline(countCP(), iCP, bez);
}
//! Draw the segment.
/*! Current position of the \a painter is already on first control
point. */
void CurveSegment::draw(Painter &painter) const
{
switch (type()) {
case ESegment:
painter.lineTo(cp(1));
break;
case EOldSpline:
case ESpline: {
std::vector bez;
beziers(bez);
for (const auto & b : bez)
painter.curveTo(b);
break; }
case EArc:
painter.drawArc(arc());
break;
}
}
//! Add segment to bounding box.
/*! Does not assume that first control point has already been added.
If \a cpf is true, then control points of splines, Bezier curves,
and the center of arcs are included in the bbox (so that snapping
can find them). Otherwise, a tight bounding box for the geometric
object itself is computed.
*/
void CurveSegment::addToBBox(Rect &box, const Matrix &m, bool cpf) const
{
switch (type()) {
case ESegment:
box.addPoint(m * cp(0));
box.addPoint(m * cp(1));
break;
case EArc:
box.addRect((m * arc()).bbox());
if (cpf)
box.addPoint((m * matrix()).translation());
break;
case ESpline:
case EOldSpline:
if (cpf) {
for (int i = 0; i < countCP(); ++i)
box.addPoint(m * cp(i));
} else {
std::vector bez;
beziers(bez);
for (const auto & b : bez)
box.addRect((m * b).bbox());
}
break;
}
}
//! Return distance to the segment.
double CurveSegment::distance(const Vector &v, const Matrix &m,
double bound) const
{
switch (type()) {
case ESegment:
return Segment(m * cp(0), m * cp(1)).distance(v, bound);
case EArc:
return (m * arc()).distance(v, bound);
case EOldSpline:
case ESpline: {
std::vector bez;
beziers(bez);
double d = bound;
double d1;
for (const auto & b : bez) {
if ((d1 = (m * b).distance(v, d)) < d)
d = d1;
}
return d; }
default: // make compiler happy
return bound;
}
}
//! Snap to vertex of the segment.
/*! The method assumes that the first control point has already been
tested. */
void CurveSegment::snapVtx(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound, bool ctl) const
{
switch (type()) {
case ESegment:
if (ctl)
// segment midpoint
snapVertex(mouse, m * (0.5 * (cp(0) + cp(1))), pos, bound);
else
snapVertex(mouse, m * cp(1), pos, bound);
break;
case EArc:
// snap to center and endpoints
if (ctl)
// center
snapVertex(mouse, (m * matrix()).translation(), pos, bound);
else
snapVertex(mouse, m * cp(1), pos, bound);
break;
case ESpline:
case EOldSpline:
// real end point is cp(countCP() - 1)
// snap to all control points
if (ctl) {
for (int i = 1; i < countCP() - 1; ++i)
snapVertex(mouse, m * cp(i), pos, bound);
} else
snapVertex(mouse, m * cp(countCP() - 1), pos, bound);
break;
}
}
void CurveSegment::snapBnd(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
switch (type()) {
case ESegment:
Segment(m * cp(0), m * cp(1)).snap(mouse, pos, bound);
break;
case EArc: {
Arc a = m * arc();
Vector pos1;
Angle angle;
double d1 = a.distance(mouse, bound, pos1, angle);
if (d1 < bound) {
bound = d1;
pos = pos1;
}
break; }
case EOldSpline:
case ESpline: {
std::vector bez;
beziers(bez);
for (const auto & b : bez)
snapBezier(mouse, m * b, pos, bound);
break; }
}
}
// --------------------------------------------------------------------
/*! \class ipe::Curve
\ingroup geo
\brief Subpath consisting of a sequence of CurveSegment's.
*/
//! Create an empty, open subpath
Curve::Curve()
{
iClosed = false;
}
//! Append a straight segment to the subpath.
void Curve::appendSegment(const Vector &v0, const Vector &v1)
{
if (iSeg.empty())
iCP.push_back(v0);
assert(v0 == iCP.back());
iCP.push_back(v1);
Seg seg;
seg.iType = CurveSegment::ESegment;
seg.iLastCP = iCP.size() - 1;
seg.iMatrix = iM.size() - 1;
iSeg.push_back(seg);
}
//! Append elliptic arc to the subpath.
void Curve::appendArc(const Matrix &m, const Vector &v0, const Vector &v1)
{
if (iSeg.empty())
iCP.push_back(v0);
assert(v0 == iCP.back());
iCP.push_back(v1);
iM.push_back(m);
Seg seg;
seg.iType = CurveSegment::EArc;
seg.iLastCP = iCP.size() - 1;
seg.iMatrix = iM.size() - 1;
iSeg.push_back(seg);
}
//! Append B-spline curve.
void Curve::appendSpline(const std::vector &v, CurveSegment::Type type)
{
assert(type == CurveSegment::ESpline ||
type == CurveSegment::EOldSpline);
if (iSeg.empty())
iCP.push_back(v[0]);
assert(v[0] == iCP.back());
for (int i = 1; i < size(v); ++i)
iCP.push_back(v[i]);
Seg seg;
seg.iType = type;
seg.iLastCP = iCP.size() - 1;
seg.iMatrix = iM.size() - 1;
iSeg.push_back(seg);
}
//! Set whether subpath is closed or not.
void Curve::setClosed(bool closed)
{
iClosed = closed;
}
SubPath::Type Curve::type() const
{
return ECurve;
}
const Curve *Curve::asCurve() const
{
return this;
}
//! Return segment.
/*! If \a i is negative, elements from the end are returned. The
closing segment of a closed path is not accessible this way (use
closingSegment() instead)!
*/
CurveSegment Curve::segment(int i) const
{
if (i < 0)
i += iSeg.size();
const Seg &seg = iSeg[i];
const Matrix *m = &iM[seg.iMatrix];
int cpbg = (i > 0) ? iSeg[i-1].iLastCP : 0;
const Vector *cp = &iCP[cpbg];
return CurveSegment(seg.iType, seg.iLastCP - cpbg + 1, cp, m);
}
void Curve::save(Stream &stream) const
{
// moveto first control point
stream << iCP[0] << " m\n";
int vtx = 1; // next control point
int mat = 0;
for (std::vector::const_iterator it = iSeg.begin();
it != iSeg.end(); ++it) {
switch (it->iType) {
case CurveSegment::ESegment:
assert(vtx == it->iLastCP);
stream << iCP[vtx++] << " l\n";
break;
case CurveSegment::EArc:
assert(vtx == it->iLastCP && mat == it->iMatrix);
stream << iM[mat++] << " " << iCP[vtx++] << " a\n";
break;
case CurveSegment::EOldSpline:
while (vtx < it->iLastCP)
stream << iCP[vtx++] << "\n";
stream << iCP[vtx++] << " s\n";
break;
case CurveSegment::ESpline:
while (vtx < it->iLastCP)
stream << iCP[vtx++] << "\n";
stream << iCP[vtx++] << " c\n";
break;
}
}
if (closed())
stream << "h\n";
}
void Curve::draw(Painter &painter) const
{
painter.moveTo(iCP[0]);
for (int i = 0; i < countSegments(); ++i)
segment(i).draw(painter);
if (closed())
painter.closePath();
}
void Curve::addToBBox(Rect &box, const Matrix &m, bool cp) const
{
for (int i = 0; i < countSegments(); ++i)
segment(i).addToBBox(box, m, cp);
}
double Curve::distance(const Vector &v, const Matrix &m,
double bound) const
{
double d = bound;
for (int i = 0; i < countSegments(); ++i) {
double d1 = segment(i).distance(v, m, d);
if (d1 < d)
d = d1;
}
if (closed()) {
Vector u[2];
double d1 = closingSegment(u).distance(v, m , d);
if (d1 < d)
d = d1;
}
return d;
}
void Curve::snapVtx(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound, bool ctl) const
{
if (!ctl)
snapVertex(mouse, m * segment(0).cp(0), pos, bound);
else if (iClosed)
// midpoint of closing segment
snapVertex(mouse, m * (0.5 * (iCP.back() + iCP.front())), pos, bound);
for (int i = 0; i < countSegments(); ++i)
segment(i).snapVtx(mouse, m, pos, bound, ctl);
}
void Curve::snapBnd(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
snapVertex(mouse, m * segment(0).cp(0), pos, bound);
for (int i = 0; i < countSegments(); ++i)
segment(i).snapBnd(mouse, m, pos, bound);
if (closed()) {
Vector u[2];
closingSegment(u).snapBnd(mouse, m, pos, bound);
}
}
//! Returns the closing segment of a closed path.
/*! Since the closing segment isn't actually stored inside this
object, you have to provide a length-2 vector for the control points.
This method panics if the Curve is not closed.
*/
CurveSegment Curve::closingSegment(Vector u[2]) const
{
assert(iClosed);
u[0] = iCP.back();
u[1] = iCP.front();
return CurveSegment(CurveSegment::ESegment, 2, u, nullptr);
}
// --------------------------------------------------------------------
/*! \class ipe::SubPath
\ingroup geo
\brief A subpath of a Path.
A subpath is either open, or closed. There are two special kinds of
closed subpaths, namely ellipses and closed B-splines.
*/
//! Implementation of pure virtual destructor.
SubPath::~SubPath()
{
// nothing
}
//! Is this subpath closed?
/*! Default implementation returns \c true. */
bool SubPath::closed() const
{
return true;
}
//! Return this object as an Ellipse, or nullptr if it's not an ellipse.
const Ellipse *SubPath::asEllipse() const
{
return nullptr;
}
//! Return this object as an ClosedSpline, or nullptr if it's not a closed spline.
const ClosedSpline *SubPath::asClosedSpline() const
{
return nullptr;
}
//! Return this object as an Curve, or else nullptr.
const Curve *SubPath::asCurve() const
{
return nullptr;
}
// --------------------------------------------------------------------
/*! \class ipe::Ellipse
\ingroup geo
\brief An ellipse subpath
*/
Ellipse::Ellipse(const Matrix &m)
: iM(m)
{
// nothing
}
SubPath::Type Ellipse::type() const
{
return EEllipse;
}
const Ellipse *Ellipse::asEllipse() const
{
return this;
}
void Ellipse::save(Stream &stream) const
{
stream << matrix() << " e\n";
}
void Ellipse::draw(Painter &painter) const
{
painter.drawArc(Arc(iM));
}
void Ellipse::addToBBox(Rect &box, const Matrix &m, bool cp) const
{
box.addRect(Arc(m * iM).bbox());
}
double Ellipse::distance(const Vector &v, const Matrix &m,
double bound) const
{
Arc arc(m * iM);
return arc.distance(v, bound);
}
//! snaps to center of ellipse.
void Ellipse::snapVtx(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound, bool ctl) const
{
if (ctl)
snapVertex(mouse, (m * iM).translation(), pos, bound);
}
void Ellipse::snapBnd(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
Arc arc(m * iM);
Vector pos1;
Angle angle;
double d1 = arc.distance(mouse, bound, pos1, angle);
if (d1 < bound) {
bound = d1;
pos = pos1;
}
}
// --------------------------------------------------------------------
/*! \class ipe::ClosedSpline
\ingroup geo
\brief A closed B-spline curve.
*/
ClosedSpline::ClosedSpline(const std::vector &v)
{
assert(v.size() >= 3);
std::copy(v.begin(), v.end(), std::back_inserter(iCP));
}
SubPath::Type ClosedSpline::type() const
{
return EClosedSpline;
}
const ClosedSpline *ClosedSpline::asClosedSpline() const
{
return this;
}
void ClosedSpline::save(Stream &stream) const
{
for (int i = 0; i < size(iCP) - 1; ++i)
stream << iCP[i] << "\n";
stream << iCP.back() << " u\n";
}
void ClosedSpline::draw(Painter &painter) const
{
std::vector bez;
beziers(bez);
painter.moveTo(bez.front().iV[0]);
for (const auto & b : bez)
painter.curveTo(b);
painter.closePath();
}
void ClosedSpline::addToBBox(Rect &box, const Matrix &m, bool cpf) const
{
if (cpf) {
for (const auto & cp : iCP)
box.addPoint(m * cp);
} else {
std::vector bez;
beziers(bez);
for (const auto & b : bez)
box.addRect((m * b).bbox());
}
}
double ClosedSpline::distance(const Vector &v, const Matrix &m,
double bound) const
{
std::vector bez;
beziers(bez);
double d = bound;
double d1;
for (const auto & b : bez) {
if ((d1 = (m * b).distance(v, d)) < d)
d = d1;
}
return d;
}
void ClosedSpline::beziers(std::vector &bez) const
{
Bezier::closedSpline(iCP.size(), &iCP.front(), bez);
}
void ClosedSpline::snapVtx(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound, bool ctl) const
{
if (ctl) {
// snap to control points
for (const auto & cp : iCP)
snapVertex(mouse, m * cp, pos, bound);
}
}
void ClosedSpline::snapBnd(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
std::vector bez;
beziers(bez);
for (const auto & b : bez)
snapBezier(mouse, m * b, pos, bound);
}
// --------------------------------------------------------------------
/*! \class ipe::Shape
\ingroup geo
\brief A geometric shape, consisting of several (open or closed) subpaths.
This class represents vector graphics geometry following the PDF
"path", but is actually a bit more complicated since we add new
subtypes: arcs, parabolas, uniform B-splines (in PDF, all of these
are converted to cubic Bezier splines).
A Shape consists of a set of subpaths (SubPath), each of which
is either open or closed, and which are rendered by stroking and
filling as a whole. The distinction between open and closed is
meaningful for stroking only, for filling any open subpath is
implicitely closed. Stroking a set of subpaths is identical to
stroking them individually. This is not true for filling: using
several subpaths, one can construct objects with holes, and more
complicated pattern.
A subpath is either an Ellipse (a complete, closed ellipse), a
ClosedSpline (a closed uniform B-spline curve), or a Curve. A curve
consists of a sequence of segments. Segments are either straight, a
quadratic Bezier spline, a cubic Bezier spline, an elliptic arc, or
a uniform cubic B-spline.
Shape is implemented using reference counting and can be copied and
passed by value efficiently. The only mutator methods are
appendSubPath() and load(), which can only be called during
construction of the Shape (that is, before its implementation has
been shared).
*/
//! Construct an empty shape (zero subpaths).
Shape::Shape()
{
iImp = new Imp;
iImp->iRefCount = 1;
}
//! Copy constructor (constant time).
Shape::Shape(const Shape &rhs)
{
iImp = rhs.iImp;
iImp->iRefCount++;
}
//! Destructor (takes care of reference counting).
Shape::~Shape()
{
if (iImp->iRefCount == 1)
delete iImp;
else
iImp->iRefCount--;
}
//! Assignment operator (constant-time).
Shape &Shape::operator=(const Shape &rhs)
{
if (this != &rhs) {
if (iImp->iRefCount == 1)
delete iImp;
else
iImp->iRefCount--;
iImp = rhs.iImp;
iImp->iRefCount++;
}
return *this;
}
// --------------------------------------------------------------------
//! Convenience function: create a rectangle shape.
Shape::Shape(const Rect &rect)
{
iImp = new Imp;
iImp->iRefCount = 1;
Curve *sp = new Curve;
sp->appendSegment(rect.bottomLeft(), rect.bottomRight());
sp->appendSegment(rect.bottomRight(), rect.topRight());
sp->appendSegment(rect.topRight(), rect.topLeft());
sp->setClosed(true);
appendSubPath(sp);
}
//! Convenience function: create a single line segment.
Shape::Shape(const Segment &seg)
{
iImp = new Imp;
iImp->iRefCount = 1;
Curve *sp = new Curve;
sp->appendSegment(seg.iP, seg.iQ);
appendSubPath(sp);
}
//! Convenience function: create circle with \a center and \a radius.
Shape::Shape(const Vector ¢er, double radius)
{
iImp = new Imp;
iImp->iRefCount = 1;
appendSubPath(new Ellipse(Matrix(radius, 0.0, 0.0, radius,
center.x, center.y)));
}
//! Convenience function: create circular arc.
/*! If \a alpha1 is larger than \a alpha0, the arc is oriented positively,
otherwise negatively. */
Shape::Shape(const Vector ¢er, double radius,
double alpha0, double alpha1)
{
iImp = new Imp;
iImp->iRefCount = 1;
Matrix m = Matrix(radius, 0, 0, radius, center.x, center.y);
Vector v0 = m * Vector(Angle(alpha0));
Vector v1 = m * Vector(Angle(alpha1));
if (alpha1 < alpha0)
// negative orientation
m = m * Linear(1, 0, 0, -1);
Curve *sp = new Curve;
sp->appendArc(m, v0, v1);
appendSubPath(sp);
}
// --------------------------------------------------------------------
//! Is this Shape a single straight segment?
bool Shape::isSegment() const
{
if (countSubPaths() != 1)
return false;
const SubPath *p = subPath(0);
if (p->type() != SubPath::ECurve || p->closed())
return false;
const Curve *c = p->asCurve();
if (c->countSegments() != 1 ||
c->segment(0).type() != CurveSegment::ESegment)
return false;
return true;
}
//! Add shape (transformed by \a m) to \a box.
void Shape::addToBBox(Rect &box, const Matrix &m, bool cp) const
{
for (int i = 0; i < countSubPaths(); ++i)
subPath(i)->addToBBox(box, m, cp);
}
double Shape::distance(const Vector &v, const Matrix &m, double bound) const
{
double d = bound;
for (int i = 0; i < countSubPaths(); ++i) {
double d1 = subPath(i)->distance(v, m, d);
if (d1 < d)
d = d1;
}
return d;
}
void Shape::snapVtx(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound, bool ctl) const
{
for (int i = 0; i < countSubPaths(); ++i)
subPath(i)->snapVtx(mouse, m, pos, bound, ctl);
}
void Shape::snapBnd(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
for (int i = 0; i < countSubPaths(); ++i)
subPath(i)->snapBnd(mouse, m, pos, bound);
}
//! Append a SubPath to shape.
/*! The Shape will take ownership of the subpath. This method can
only be used during construction of the Shape. It will panic if the
implementation has been shared.
*/
void Shape::appendSubPath(SubPath *sp)
{
assert(iImp->iRefCount == 1);
iImp->iSubPaths.push_back(sp);
}
//! Draw the Shape as a path to \a painter.
/*! Does not call newPath() on \a painter. */
void Shape::draw(Painter &painter) const
{
for (int i = 0; i < countSubPaths(); ++i)
subPath(i)->draw(painter);
}
Shape::Imp::~Imp()
{
// delete the subpaths
for (SubPathSeq::iterator it = iSubPaths.begin();
it != iSubPaths.end(); ++it) {
delete *it;
*it = nullptr;
}
}
static Vector getVector(std::vector &args)
{
Vector v;
v.x = args[0];
v.y = args[1];
args.erase(args.begin(), args.begin() + 2);
return v;
}
static Matrix getMatrix(std::vector &args)
{
Matrix m;
for (int i = 0; i < 6; ++i)
m.a[i] = args[i];
args.erase(args.begin(), args.begin() + 6);
return m;
}
//! Save Shape onto XML stream.
void Shape::save(Stream &stream) const
{
for (int i = 0; i < countSubPaths(); ++i)
subPath(i)->save(stream);
}
//! Create a Shape from XML data.
/*! Appends subpaths from XML data to the current Shape. Returns
false if the path syntax is incorrect (the Shape will be in an
inconsistent state and must be discarded).
This method can only be used during construction of the Shape. It
will panic if the implementation has been shared. */
bool Shape::load(String data)
{
assert(iImp->iRefCount == 1);
Lex stream(data);
String word;
String type;
Curve *sp = nullptr;
Vector org;
std::vector args;
do {
if (stream.token() == "h") { // closing path
if (!sp)
return false;
stream.nextToken(); // eat token
sp->setClosed(true);
sp = nullptr;
} else if (stream.token() == "m") {
if (args.size() != 2)
return false;
stream.nextToken(); // eat token
// begin new subpath
sp = new Curve;
appendSubPath(sp);
org = getVector(args);
} else if (stream.token() == "l") {
// assert(sp && args.size() > 0 && (args.size() % 2 == 0));
if (!sp || args.size() != 2)
return false;
stream.nextToken(); // eat token
while (!args.empty()) {
Vector v = getVector(args);
sp->appendSegment(org, v);
org = v;
}
} else if (stream.token() == "a") {
if (!sp || args.size() != 8)
return false;
stream.nextToken();
Matrix m = getMatrix(args);
if (m.determinant() == 0)
return false; // don't accept zero-radius arc
Vector v1 = getVector(args);
sp->appendArc(m, org, v1);
org = v1;
} else if (stream.token() == "s" || stream.token() == "q"
|| stream.token() == "c") {
if (!sp || args.size() < 2 || (args.size() % 2 != 0))
return false;
bool oldStyle = (stream.token() == "s");
stream.nextToken();
std::vector v;
v.push_back(org);
while (!args.empty())
v.push_back(getVector(args));
if (oldStyle)
sp->appendOldSpline(v);
else
sp->appendSpline(v);
org = v.back();
} else if (stream.token() == "e") {
if (args.size() != 6)
return false;
stream.nextToken();
sp = nullptr;
Ellipse *e = new Ellipse(getMatrix(args));
appendSubPath(e);
} else if (stream.token() == "u") {
if (args.size() < 6 || (args.size() % 2 != 0))
return false;
stream.nextToken();
sp = nullptr;
std::vector v;
while (!args.empty())
v.push_back(getVector(args));
ClosedSpline *e = new ClosedSpline(v);
appendSubPath(e);
} else { // must be a number
double num;
stream >> num;
args.push_back(num);
}
stream.skipWhitespace();
} while (!stream.eos());
// sanity checks
if (countSubPaths() == 0)
return false;
for (int i = 0; i < countSubPaths(); ++i) {
if (subPath(i)->asCurve() && subPath(i)->asCurve()->countSegments() == 0)
return false;
}
return true;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipetoolbase.cpp 0000644 0001750 0001750 00000005052 13561570220 017303 0 ustar otfried otfried // --------------------------------------------------------------------
// ipe::Tool
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipetoolbase.h"
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::Tool
\ingroup canvas
\brief Abstract base class for various canvas tools.
The Canvas doesn't know about the various modes for object creation,
editing, and moving, but delegates the handling to a subclass of
Tool.
*/
//! Constructor.
Tool::Tool(CanvasBase *canvas) : iCanvas(canvas)
{
// nothing
}
//! Virtual destructor.
Tool::~Tool()
{
// nothing
}
//! Called when a mouse button is pressed or released on the canvas.
/*! \a button is 1, 2, or 3, with Shift/Ctrl/Alt/Meta modifiers added
in (as defined in CanvasBase::TModifiers. \a press is true for
button-down, and false for button-up. */
void Tool::mouseButton(int button, bool press)
{
// ignore it
}
//! Called when the mouse is moved on the canvas.
void Tool::mouseMove()
{
// ignore it
}
//! Called when a key is pressed.
/*! \a modifiers are as defined in CanvasBase::TModifiers. */
bool Tool::key(String text, int modifiers)
{
return false; // not handled
}
//! Snapping to vertices on object currently being drawn.
void Tool::snapVtx(const Vector &mouse, Vector &pos,
double &bound, bool cp) const
{
// nothing
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipepath.cpp 0000644 0001750 0001750 00000051072 13561570220 016432 0 ustar otfried otfried // --------------------------------------------------------------------
// The path object
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipepath.h"
#include "ipepainter.h"
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::Path
\ingroup obj
\brief The path object (polylines, polygons, and generalizations).
This object represents any vector graphics. The geometry is
contained in a Shape.
The filling algorithm is the even-odd rule of PDF: To
determine whether a point lies inside the filled shape, draw a ray
from that point in any direction, and count the number of path
segments that cross the ray. If this number is odd, the point is
inside; if even, the point is outside. (Path objects can also render
using the winding fill rule by setting the fillRule
attribute. This isn't really supported by the Ipe user interface,
which doesn't show the orientation of paths.)
If the path consists of a single line segment and is filled only,
then it is not drawn at all. This can be used to draw arrow heads
without bodies. The fill color is used to draw the arrows in this
case.
*/
//! Construct from XML data.
Path *Path::create(const XmlAttributes &attr, String data)
{
std::unique_ptr self(new Path(attr));
if (!self->iShape.load(data))
return nullptr;
self->makeArrowData();
return self.release();
}
//! Create empty path with attributes taken from XML
Path::Path(const XmlAttributes &attr)
: Object(attr)
{
bool stroked = false;
bool filled = false;
iStroke = Attribute::BLACK();
iFill = Attribute::WHITE();
String str;
if (attr.has("stroke", str)) {
iStroke = Attribute::makeColor(str, Attribute::BLACK());
stroked = true;
}
if (attr.has("fill", str)) {
iFill = Attribute::makeColor(str, Attribute::WHITE());
filled = true;
}
if (!stroked && !filled) {
stroked = true;
iStroke= Attribute::BLACK();
}
iDashStyle = Attribute::makeDashStyle(attr["dash"]);
iPen = Attribute::makeScalar(attr["pen"], Attribute::NORMAL());
if (attr.has("opacity", str))
iOpacity = Attribute(true, str);
else
iOpacity = Attribute::OPAQUE();
if (attr.has("stroke-opacity", str))
iStrokeOpacity = Attribute(true, str);
else
iStrokeOpacity = iOpacity;
iGradient = Attribute::NORMAL();
iTiling = Attribute::NORMAL();
if (attr.has("gradient", str))
iGradient = Attribute(true, str);
else if (attr.has("tiling", str))
iTiling = Attribute(true, str);
iPathMode = (stroked ? (filled ? EStrokedAndFilled : EStrokedOnly) :
EFilledOnly);
iLineCap = EDefaultCap;
iLineJoin = EDefaultJoin;
iFillRule = EDefaultRule;
if (attr.has("cap", str))
iLineCap = TLineCap(Lex(str).getInt() + 1);
if (attr.has("join", str))
iLineJoin = TLineJoin(Lex(str).getInt() + 1);
if (attr.has("fillrule", str)) {
if (str == "eofill")
iFillRule = EEvenOddRule;
else if (str == "wind")
iFillRule = EWindRule;
}
iHasFArrow = false;
iHasRArrow = false;
iFArrowShape = iRArrowShape = Attribute::ARROW_NORMAL();
iFArrowSize = iRArrowSize = Attribute::NORMAL();
if (attr.has("arrow", str)) {
iHasFArrow = true;
int i = str.find("/");
if (i >= 0) {
iFArrowShape = Attribute(true, String("arrow/") + str.left(i) + "(spx)");
iFArrowSize = Attribute::makeScalar(str.substr(i+1), Attribute::NORMAL());
} else
iFArrowSize = Attribute::makeScalar(str, Attribute::NORMAL());
}
if (attr.has("rarrow", str)) {
iHasRArrow = true;
int i = str.find("/");
if (i >= 0) {
iRArrowShape = Attribute(true, String("arrow/") + str.left(i) + "(spx)");
iRArrowSize = Attribute::makeScalar(str.substr(i+1), Attribute::NORMAL());
} else
iRArrowSize = Attribute::makeScalar(str, Attribute::NORMAL());
}
}
void Path::init(const AllAttributes &attr, bool withArrows)
{
iPathMode = attr.iPathMode;
iStroke = attr.iStroke;
iFill = attr.iFill;
iDashStyle = attr.iDashStyle;
iPen = attr.iPen;
iOpacity = attr.iOpacity;
iStrokeOpacity = attr.iStrokeOpacity;
iTiling = attr.iTiling;
if (iTiling.isNormal())
iGradient = attr.iGradient;
else
iGradient = Attribute::NORMAL();
iLineCap = attr.iLineCap;
iLineJoin = attr.iLineJoin;
iFillRule = attr.iFillRule;
iHasFArrow = false;
iHasRArrow = false;
iFArrowShape = iRArrowShape = Attribute::ARROW_NORMAL();
iFArrowSize = iRArrowSize = Attribute::NORMAL();
if (withArrows) {
iFArrowShape = attr.iFArrowShape;
iRArrowShape = attr.iRArrowShape;
iFArrowSize = attr.iFArrowSize;
iRArrowSize = attr.iRArrowSize;
iHasFArrow = attr.iFArrow;
iHasRArrow = attr.iRArrow;
}
}
//! Create for given shape.
Path::Path(const AllAttributes &attr, const Shape &shape, bool withArrows)
: Object(attr), iShape(shape)
{
init(attr, withArrows);
makeArrowData();
}
//! Return a clone (constant-time).
Object *Path::clone() const
{
return new Path(*this);
}
//! Compute the arrow information.
void Path::makeArrowData()
{
assert(iShape.countSubPaths() > 0);
if (iShape.countSubPaths() > 1 || iShape.subPath(0)->closed()) {
iFArrowOk = false;
iRArrowOk = false;
} else {
CurveSegment seg = iShape.subPath(0)->asCurve()->segment(0);
iRArrowOk = true;
iRArrowPos = seg.cp(0);
iRArrowArc = 0;
if (seg.type() == CurveSegment::EArc) {
iRArrowArc = 1;
Angle alpha = (seg.matrix().inverse() * seg.cp(0)).angle();
Linear m = seg.matrix().linear();
iRArrowDir = (m * Vector(Angle(alpha - IpeHalfPi))).angle();
} else {
if (seg.cp(1) == seg.cp(0))
iRArrowOk = false;
else
iRArrowDir = (iRArrowPos - seg.cp(1)).angle();
}
seg = iShape.subPath(0)->asCurve()->segment(-1);
iFArrowOk = true;
iFArrowPos = seg.last();
iFArrowArc = 0;
if (seg.type() == CurveSegment::EArc) {
iFArrowArc = 1;
Angle alpha = (seg.matrix().inverse() * seg.cp(1)).angle();
Linear m = seg.matrix().linear();
iFArrowDir = (m * Vector(Angle(alpha + IpeHalfPi))).angle();
} else {
if (seg.cp(seg.countCP() - 2) == seg.last())
iFArrowOk = false;
else
iFArrowDir = (iFArrowPos - seg.cp(seg.countCP() - 2)).angle();
}
}
}
//! Return pointer to this object.
Path *Path::asPath()
{
return this;
}
Object::Type Path::type() const
{
return EPath;
}
//! Call visitPath of visitor.
void Path::accept(Visitor &visitor) const
{
visitor.visitPath(this);
}
void Path::saveAsXml(Stream &stream, String layer) const
{
bool stroked = (iPathMode <= EStrokedAndFilled);
bool filled = (iPathMode >= EStrokedAndFilled);
stream << "\n";
iShape.save(stream);
stream << "\n";
}
/*! Draw an arrow of \a size with tip at \a pos directed
in direction \a angle. */
void Path::drawArrow(Painter &painter, Vector pos, Angle angle,
Attribute shape, Attribute size, double radius)
{
const Symbol *symbol = painter.cascade()->findSymbol(shape);
if (symbol) {
double s = painter.cascade()->find(EArrowSize, size).number().toDouble();
Color color = painter.stroke();
// Fixed opaq = painter.opacity();
painter.push();
painter.pushMatrix();
painter.translate(pos);
painter.transform(Linear(angle));
painter.untransform(ETransformationsRigidMotions);
bool cw = (radius < 0);
if (cw)
radius = -radius;
bool pointy = (shape == Attribute::ARROW_PTARC() ||
shape == Attribute::ARROW_FPTARC());
if (shape.isArcArrow() && (radius > s)) {
Angle delta = s / radius;
Angle alpha = atan(1.0/3.0);
Arc arc1;
Arc arc2;
Arc arc3;
if (cw) {
arc1 = Arc(Matrix(radius, 0, 0, radius, 0, -radius),
IpeHalfPi, IpeHalfPi + delta);
arc2 = Arc(Matrix(radius, 0, 0, -radius, 0, -radius),
-IpeHalfPi - delta, -IpeHalfPi);
arc3 = Arc(Matrix(radius, 0, 0, radius, 0, -radius),
IpeHalfPi, IpeHalfPi + 0.8 * delta);
} else {
arc1 = Arc(Matrix(radius, 0, 0, radius, 0, radius),
-IpeHalfPi - delta, -IpeHalfPi);
arc2 = Arc(Matrix(radius, 0, 0, -radius, 0, radius),
IpeHalfPi, IpeHalfPi + delta);
arc3 = Arc(Matrix(radius, 0, 0, radius, 0, radius),
-IpeHalfPi - 0.8 * delta, -IpeHalfPi);
}
arc1 = Linear(alpha) * arc1;
arc2 = Linear(-alpha) * arc2;
painter.setStroke(Attribute(color));
if (shape == Attribute::ARROW_FARC() ||
shape == Attribute::ARROW_FPTARC())
painter.setFill(Attribute(Color(1000, 1000, 1000)));
else
painter.setFill(Attribute(color));
// painter.setOpacity(Attribute(opaq));
painter.newPath();
painter.moveTo(arc1.beginp());
painter.drawArc(arc1);
if (cw) {
if (pointy)
painter.lineTo(arc3.endp());
painter.lineTo(arc2.beginp());
painter.drawArc(arc2);
} else {
painter.drawArc(arc2);
if (pointy)
painter.lineTo(arc3.beginp());
}
painter.closePath();
painter.drawPath(EStrokedAndFilled);
} else {
Matrix m(s, 0, 0, s, 0, 0);
painter.transform(m);
painter.setSymStroke(Attribute(color));
painter.setSymFill(Attribute(color));
painter.setSymPen(Attribute(painter.pen()));
symbol->iObject->draw(painter);
}
painter.popMatrix();
painter.pop();
}
}
void Path::setShape(const Shape &shape)
{
iShape = shape;
makeArrowData();
}
void Path::draw(Painter &painter) const
{
painter.push();
if (iPathMode <= EStrokedAndFilled) {
painter.setStroke(iStroke);
painter.setDashStyle(iDashStyle);
painter.setPen(iPen);
painter.setLineCap(lineCap());
painter.setLineJoin(lineJoin());
}
if (iPathMode >= EStrokedAndFilled) {
painter.setFill(iFill);
painter.setFillRule(fillRule());
painter.setTiling(iTiling);
painter.setGradient(iGradient);
}
painter.setOpacity(iOpacity);
painter.setStrokeOpacity(iStrokeOpacity);
painter.pushMatrix();
painter.transform(matrix());
painter.untransform(transformations());
if (!iShape.isSegment() || iPathMode != EFilledOnly) {
painter.newPath();
iShape.draw(painter);
painter.drawPath(iPathMode);
}
if (iPathMode == EStrokedAndFilled && !iGradient.isNormal()) {
// need to stroke separately
painter.newPath();
iShape.draw(painter);
painter.drawPath(EStrokedOnly);
}
if ((iHasFArrow && iFArrowOk) || (iHasRArrow && iRArrowOk)) {
// Draw arrows
if (iPathMode == EFilledOnly) {
painter.setStroke(iFill);
painter.setPen(iPen);
painter.setLineCap(lineCap());
painter.setLineJoin(lineJoin());
}
if (iHasFArrow && iFArrowOk) {
double r = 0.0;
if (iFArrowArc && iFArrowShape.isArcArrow()) {
CurveSegment seg = iShape.subPath(0)->asCurve()->segment(-1);
Vector center = painter.matrix() * seg.matrix().translation();
r = (center - painter.matrix() * iFArrowPos).len();
if ((painter.matrix().linear() * seg.matrix().linear()).determinant()<0)
r = -r;
}
drawArrow(painter, iFArrowPos, iFArrowDir, iFArrowShape, iFArrowSize, r);
}
if (iHasRArrow && iRArrowOk) {
double r = 0.0;
if (iRArrowArc && iRArrowShape.isArcArrow()) {
CurveSegment seg = iShape.subPath(0)->asCurve()->segment(0);
Vector center = painter.matrix() * seg.matrix().translation();
r = (center - painter.matrix() * iRArrowPos).len();
if ((painter.matrix().linear() * seg.matrix().linear()).determinant()>0)
r = -r;
}
drawArrow(painter, iRArrowPos, iRArrowDir, iRArrowShape, iRArrowSize, r);
}
}
painter.popMatrix();
painter.pop();
}
void Path::drawSimple(Painter &painter) const
{
painter.pushMatrix();
painter.transform(matrix());
painter.untransform(transformations());
painter.newPath();
iShape.draw(painter);
painter.drawPath(EStrokedOnly);
painter.popMatrix();
}
void Path::addToBBox(Rect &box, const Matrix &m, bool cp) const
{
iShape.addToBBox(box, m * matrix(), cp);
}
double Path::distance(const Vector &v, const Matrix &m, double bound) const
{
return iShape.distance(v, m * matrix(), bound);
}
void Path::snapVtx(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
iShape.snapVtx(mouse, m * matrix(), pos, bound, false);
}
void Path::snapCtl(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
iShape.snapVtx(mouse, m * matrix(), pos, bound, true);
}
void Path::snapBnd(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
iShape.snapBnd(mouse, m * matrix(), pos, bound);
}
//! Set whether object will be stroked and filled.
void Path::setPathMode(TPathMode pm)
{
iPathMode = pm;
}
//! Set stroke color.
void Path::setStroke(Attribute stroke)
{
iStroke = stroke;
}
//! Set fill color.
void Path::setFill(Attribute fill)
{
iFill = fill;
}
//! Set tiling pattern of the object.
/*! Resets gradient fill. */
void Path::setTiling(Attribute til)
{
iTiling = til;
iGradient = Attribute::NORMAL();
}
//! Set gradient fill of the object.
/*! Resets tiling pattern. */
void Path::setGradient(Attribute grad)
{
iGradient = grad;
iTiling = Attribute::NORMAL();
}
//! Set opacity of the object.
void Path::setOpacity(Attribute opaq)
{
iOpacity = opaq;
}
//! Set stroke opacity of the object.
void Path::setStrokeOpacity(Attribute opaq)
{
iStrokeOpacity = opaq;
}
//! Set pen.
void Path::setPen(Attribute pen)
{
iPen = pen;
}
//! Set dash style.
void Path::setDashStyle(Attribute dash)
{
iDashStyle = dash;
}
//! Set forward arrow.
void Path::setArrow(bool arrow, Attribute shape, Attribute size)
{
iHasFArrow = arrow;
iFArrowShape = shape;
iFArrowSize = size;
}
//! Set backward arrow (if the object can take it).
void Path::setRarrow(bool arrow, Attribute shape, Attribute size)
{
iHasRArrow = arrow;
iRArrowShape = shape;
iRArrowSize = size;
}
//! Set line cap style.
void Path::setLineCap(TLineCap s)
{
iLineCap = s;
}
//! Set line join style.
void Path::setLineJoin(TLineJoin s)
{
iLineJoin = s;
}
//! Set fill rule.
void Path::setFillRule(TFillRule s)
{
iFillRule = s;
}
void Path::checkStyle(const Cascade *sheet, AttributeSeq &seq) const
{
checkSymbol(EColor, iStroke, sheet, seq);
checkSymbol(EColor, iFill, sheet, seq);
checkSymbol(EDashStyle, iDashStyle, sheet, seq);
checkSymbol(EPen, iPen, sheet, seq);
checkSymbol(EArrowSize, iFArrowSize, sheet, seq);
checkSymbol(EArrowSize, iRArrowSize, sheet, seq);
checkSymbol(ESymbol, iFArrowShape, sheet, seq);
checkSymbol(ESymbol, iRArrowShape, sheet, seq);
checkSymbol(EOpacity, iOpacity, sheet, seq);
checkSymbol(EOpacity, iStrokeOpacity, sheet, seq);
if (!iTiling.isNormal())
checkSymbol(ETiling, iTiling, sheet, seq);
if (!iGradient.isNormal())
checkSymbol(EGradient, iGradient, sheet, seq);
}
bool Path::setAttribute(Property prop, Attribute value)
{
switch (prop) {
case EPropPathMode:
if (value.pathMode() != pathMode()) {
setPathMode(value.pathMode());
return true;
}
break;
case EPropStrokeColor:
if (value != stroke()) {
setStroke(value);
return true;
}
break;
case EPropFillColor:
if (value != fill()) {
setFill(value);
return true;
}
break;
case EPropPen:
if (value != pen()) {
setPen(value);
return true;
}
break;
case EPropDashStyle:
if (value != dashStyle()) {
setDashStyle(value);
return true;
}
break;
case EPropTiling:
if (value != tiling()) {
setTiling(value);
return true;
}
break;
case EPropGradient:
if (value != gradient()) {
setGradient(value);
return true;
}
break;
case EPropOpacity:
if (value != opacity()) {
setOpacity(value);
return true;
}
break;
case EPropStrokeOpacity:
if (value != strokeOpacity()) {
setStrokeOpacity(value);
return true;
}
break;
case EPropFArrow:
if (value.boolean() != iHasFArrow) {
iHasFArrow = value.boolean();
return true;
}
break;
case EPropRArrow:
if (value.boolean() != iHasRArrow) {
iHasRArrow = value.boolean();
return true;
}
break;
case EPropFArrowSize:
if (value != iFArrowSize) {
iFArrowSize= value;
return true;
}
break;
case EPropRArrowSize:
if (value != iRArrowSize) {
iRArrowSize = value;
return true;
}
break;
case EPropFArrowShape:
if (value != iFArrowShape) {
iFArrowShape = value;
return true;
}
break;
case EPropRArrowShape:
if (value != iRArrowShape) {
iRArrowShape = value;
return true;
}
break;
case EPropLineJoin:
assert(value.isEnum());
if (value.lineJoin() != iLineJoin) {
iLineJoin = value.lineJoin();
return true;
}
break;
case EPropLineCap:
assert(value.isEnum());
if (value.lineCap() != iLineCap) {
iLineCap = value.lineCap();
return true;
}
break;
case EPropFillRule:
assert(value.isEnum());
if (value.fillRule() != iFillRule) {
iFillRule = value.fillRule();
return true;
}
break;
default:
return Object::setAttribute(prop, value);
}
return false;
}
Attribute Path::getAttribute(Property prop) const noexcept
{
switch (prop) {
case EPropPathMode:
return Attribute(iPathMode);
case EPropStrokeColor:
return stroke();
case EPropFillColor:
return fill();
case EPropPen:
return pen();
case EPropDashStyle:
return dashStyle();
case EPropOpacity:
return opacity();
case EPropStrokeOpacity:
return strokeOpacity();
case EPropTiling:
return tiling();
case EPropGradient:
return gradient();
case EPropFArrow:
return Attribute::Boolean(iHasFArrow);
case EPropRArrow:
return Attribute::Boolean(iHasRArrow);
case EPropFArrowSize:
return iFArrowSize;
case EPropRArrowSize:
return iRArrowSize;
case EPropFArrowShape:
return iFArrowShape;
case EPropRArrowShape:
return iRArrowShape;
case EPropLineJoin:
return Attribute(iLineJoin);
case EPropLineCap:
return Attribute(iLineCap);
case EPropFillRule:
return Attribute(iFillRule);
default:
return Object::getAttribute(prop);
}
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipeplatform.cpp 0000644 0001750 0001750 00000043347 13561570220 017330 0 ustar otfried otfried // --------------------------------------------------------------------
// Platform dependent methods
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipebase.h"
#include "ipeattributes.h"
#ifdef WIN32
#include
#include
#include
#include
#else
#include
#include
#endif
#ifdef __APPLE__
#include
#include
// sys/param.h triggers legacy mode for realpath, so that the
// last component of the path does not need to exist.
#include
#include
#endif
#include
#include
#include
#include
#include
#include
#include
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::Platform
\ingroup base
\brief Platform dependent methods.
*/
//! Return the Ipelib version.
/*! This is available as a function so that one can verify what
version of Ipelib one has actually linked with (as opposed to the
header files used during compilation).
*/
int Platform::libVersion()
{
return IPELIB_VERSION;
}
// --------------------------------------------------------------------
static bool initialized = false;
static bool showDebug = false;
static Platform::DebugHandler debugHandler = nullptr;
#ifdef IPEAPPIMAGE
static String appImageBase;
#endif
#ifdef WIN32
static ULONG_PTR gdiplusToken = 0;
_locale_t ipeLocale;
typedef _locale_t (*LPCreateLocale)(int category, const char *locale);
static LPCreateLocale p_create_locale = nullptr;
typedef void (*LPFreeLocale)(_locale_t locale);
static LPFreeLocale p_free_locale = nullptr;
typedef double (*LPStrtodL)(const char *strSource, char **endptr, _locale_t locale);
static LPStrtodL p_strtod_l = nullptr;
#else
locale_t ipeLocale;
#endif
#ifndef WIN32
static String dotIpe()
{
const char *home = getenv("HOME");
if (!home)
return String();
String res = String(home) + "/.ipe";
if (!Platform::fileExists(res) && mkdir(res.z(), 0700) != 0)
return String();
return res + "/";
}
#endif
#ifdef WIN32
static void readIpeConf()
{
String fname = Platform::ipeDir("", nullptr);
fname += "\\ipe.conf";
String conf = Platform::readFile(fname);
if (conf.empty())
return;
ipeDebug("ipe.conf = %s", conf.z());
int i = 0;
while (i < conf.size()) {
String line = conf.getLine(i);
_wputenv(line.w().data());
}
}
#else
static void readIpeConf()
{
String fname = dotIpe() + "/ipe.conf";
String conf = Platform::readFile(fname);
if (conf.empty())
return;
ipeDebug("ipe.conf = %s", conf.z());
int i = 0;
while (i < conf.size()) {
String line = conf.getLine(i);
putenv(strdup(line.z())); // a leak, but necessary
}
}
#endif
static void debugHandlerImpl(const char *msg)
{
if (showDebug) {
fprintf(stderr, "%s\n", msg);
#ifdef WIN32
fflush(stderr);
OutputDebugStringA(msg);
#endif
}
}
static void shutdownIpelib()
{
#ifdef WIN32
Gdiplus::GdiplusShutdown(gdiplusToken);
if (p_create_locale != nullptr)
p_free_locale(ipeLocale);
#else
freelocale(ipeLocale);
#endif
Repository::cleanup();
}
//! Initialize Ipelib.
/*! This method must be called before Ipelib is used.
It creates a LC_NUMERIC locale set to 'C', which is necessary for
correct loading and saving of Ipe objects. The method also checks
that the correct version of Ipelib is loaded, and aborts with an
error message if the version is not correct. Also enables ipeDebug
messages if environment variable IPEDEBUG is defined. (You can
override this using setDebug).
*/
void Platform::initLib(int version)
{
if (initialized)
return;
initialized = true;
readIpeConf();
showDebug = false;
if (getenv("IPEDEBUG")) {
showDebug = true;
fprintf(stderr, "Debug messages enabled\n");
}
debugHandler = debugHandlerImpl;
#ifdef IPEAPPIMAGE
const char *cwd = getcwd(nullptr, 0);
if (cwd)
appImageBase = cwd;
else
appImageBase = ".";
#endif
#ifdef WIN32
HMODULE hDll = LoadLibraryA("msvcrt.dll");
if (hDll) {
p_create_locale = (LPCreateLocale) GetProcAddress(hDll, "_create_locale");
p_free_locale = (LPFreeLocale) GetProcAddress(hDll, "_free_locale");
p_strtod_l = (LPStrtodL) GetProcAddress(hDll, "_strtod_l");
}
if (p_create_locale != nullptr)
ipeLocale = p_create_locale(LC_NUMERIC, "C");
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
#else
ipeLocale = newlocale(LC_NUMERIC_MASK, "C", nullptr);
#endif
atexit(shutdownIpelib);
#ifndef IPE_NO_IPELIB_VERSION_CHECK
if (version == IPELIB_VERSION)
return;
fprintf(stderr,
"Ipetoipe has been compiled with header files for Ipelib %d\n"
"but is dynamically linked against libipe %d.\n"
"Check with 'ldd' which libipe is being loaded, and "
"replace it by the correct version or set LD_LIBRARY_PATH.\n",
version, IPELIB_VERSION);
exit(99);
#endif
}
//! Enable or disable display of ipeDebug messages.
void Platform::setDebug(bool debug)
{
showDebug = debug;
}
// --------------------------------------------------------------------
void ipeDebug(const char *msg, ...) noexcept
{
if (debugHandler) {
char buf[8196];
va_list ap;
va_start(ap, msg);
std::vsprintf(buf, msg, ap);
va_end(ap);
debugHandler(buf);
}
}
// --------------------------------------------------------------------
//! Returns current working directory.
/*! Returns empty string if something fails. */
String Platform::currentDirectory()
{
#ifdef WIN32
wchar_t *buffer = _wgetcwd(nullptr, 0);
return String(buffer);
#else
char buffer[1024];
if (getcwd(buffer, 1024) != buffer)
return String();
return String(buffer);
#endif
}
//! Returns drive on which Ipe executable exists.
/*! On Linux and OSX, returns empty string. */
String Platform::ipeDrive()
{
#ifdef WIN32
String fname = ipeDir("", nullptr);
if (fname.size() > 2 && fname[1] == ':')
return fname.left(2);
#endif
return String();
}
#ifdef IPEBUNDLE
String Platform::ipeDir(const char *suffix, const char *fname)
{
#ifdef IPESNAPCRAFT
const char *p = getenv("SNAP");
String exe = p ? p : "/snap/ipe/current";
exe += "/ipe/";
#else
#ifdef IPEAPPIMAGE
String exe = appImageBase + "/ipe/";
#else
#ifdef WIN32
wchar_t exename[OFS_MAXPATHNAME];
GetModuleFileNameW(nullptr, exename, OFS_MAXPATHNAME);
String exe(exename);
#else
char path[PATH_MAX], rpath[PATH_MAX];
uint32_t size = sizeof(path);
String exe;
if (_NSGetExecutablePath(path, &size) == 0 && realpath(path, rpath) != nullptr)
exe = String(rpath);
else
ipeDebug("ipeDir: buffer too small; need size %u", size);
#endif
int i = exe.rfind(IPESEP);
if (i >= 0) {
exe = exe.left(i); // strip filename
i = exe.rfind(IPESEP);
if (i >= 0) {
exe = exe.left(i); // strip bin directory name
}
}
#ifdef __APPLE__
if (!strcmp(suffix, "doc"))
exe += "/SharedSupport/";
else
exe += "/Resources/";
#else
exe += IPESEP;
#endif
#endif
#endif
exe += suffix;
if (fname) {
exe += IPESEP;
exe += fname;
}
return exe;
}
#endif
//! Return path for the directory containing pdflatex and xelatex.
/*! If empty means look on PATH. */
String Platform::latexPath()
{
String result;
#ifdef WIN32
const wchar_t *p = _wgetenv(L"IPELATEXPATH");
if (p)
result = String(p);
if (result.left(4) == "ipe:")
result = ipeDrive() + result.substr(4);
#else
char *p = getenv("IPELATEXPATH");
if (p)
result = p;
#endif
return result;
}
//! Returns directory for running Latex.
/*! The directory is created if it does not exist. Returns an empty
string if the directory cannot be found or cannot be created.
The directory returned ends in the path separator.
*/
String Platform::latexDirectory()
{
#ifdef WIN32
String latexDir;
const wchar_t *p = _wgetenv(L"IPELATEXDIR");
if (p) {
latexDir = String(p);
if (latexDir.left(4) == "ipe:")
latexDir = ipeDrive() + latexDir.substr(4);
} else {
wchar_t szPath[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA,
nullptr, 0, szPath))) {
latexDir = String(szPath) + "\\ipe";
} else {
p = _wgetenv(L"LOCALAPPDATA");
if (p)
latexDir = String(p) + "\\ipe";
else
latexDir = ipeDir("latexrun");
}
}
if (latexDir.right(1) == "\\")
latexDir = latexDir.left(latexDir.size() - 1);
if (!fileExists(latexDir)) {
if (Platform::mkdir(latexDir.z()) != 0)
return String();
}
latexDir += "\\";
return latexDir;
#else
const char *p = getenv("IPELATEXDIR");
String latexDir;
if (p) {
latexDir = p;
if (latexDir.right(1) == "/")
latexDir = latexDir.left(latexDir.size() - 1);
} else {
latexDir = dotIpe() + "latexrun";
}
if (!fileExists(latexDir) && mkdir(latexDir.z(), 0700) != 0)
return String();
latexDir += "/";
return latexDir;
#endif
}
//! Determine whether file exists.
bool Platform::fileExists(String fname)
{
#ifdef WIN32
return (_waccess(fname.w().data(), F_OK) == 0);
#else
return (access(fname.z(), F_OK) == 0);
#endif
}
//! Convert relative filename to absolute.
/*! This also works when the filename does not exist, or at least it tries. */
String Platform::realPath(String fname)
{
#ifdef WIN32
wchar_t wresult[MAX_PATH];
// this function works also when fname does not exist
GetFullPathNameW(fname.w().data(), MAX_PATH, wresult, nullptr);
return String(wresult);
#else
char rpath[PATH_MAX];
if (realpath(fname.z(), rpath))
return String(rpath);
if (errno != ENOENT || fname.left(1) == "/")
return fname; // not much we can do
if (realpath(".", rpath) == nullptr)
return fname; // nothing we can do
return String(rpath) + "/" + fname;
#endif
}
//! List all files in directory
/*! Return true if successful, false on error. */
bool Platform::listDirectory(String path, std::vector &files)
{
#ifdef WIN32
String pattern = path + "\\*";
struct _wfinddata_t info;
intptr_t h = _wfindfirst(pattern.w().data(), &info);
if (h == -1L)
return false;
files.push_back(String(info.name));
while (_wfindnext(h, &info) == 0)
files.push_back(String(info.name));
_findclose(h);
return true;
#else
DIR *dir = opendir(path.z());
if (dir == nullptr)
return false;
struct dirent *entry = readdir(dir);
while (entry != nullptr) {
String s(entry->d_name);
if (s != "." && s != "..")
files.push_back(s);
entry = readdir(dir);
}
closedir(dir);
return true;
#endif
}
//! Read entire file into string.
/*! Returns an empty string if file cannot be found or read.
There is no way to distinguish an empty file from this. */
String Platform::readFile(String fname)
{
std::FILE *file = Platform::fopen(fname.z(), "rb");
if (!file)
return String();
String s;
int ch;
while ((ch = std::fgetc(file)) != EOF)
s.append(ch);
std::fclose(file);
return s;
}
//! Runs latex on file ipetemp.tex in given directory.
/*! directory of docname is added to TEXINPUTS if its non-empty. */
int Platform::runLatex(String dir, LatexType engine, String docname) noexcept
{
const char *latex = (engine == LatexType::Xetex) ?
"xelatex" : (engine == LatexType::Luatex) ?
"lualatex" : "pdflatex";
String url = Platform::readFile(dir + "url.txt");
bool online = (url.left(4) == "http");
String texinputs;
if (!online && !docname.empty()) {
docname = realPath(docname);
int i = docname.size();
while (i > 0 && docname[i-1] != IPESEP)
--i;
if (i > 0)
texinputs = docname.substr(0, i-1);
}
#ifdef WIN32
if (!online && getenv("IPETEXFORMAT")) {
latex = (engine == LatexType::Xetex) ?
"xetex ^&latex" : (engine == LatexType::Luatex) ?
"luatex ^&latex" : "pdftex ^&pdflatex";
}
String bat;
if (dir.size() > 2 && dir[1] == ':') {
bat += dir.substr(0, 2);
bat += "\r\n";
}
bat += "cd \"";
bat += dir;
bat += "\"\r\n";
if (!texinputs.empty()) {
bat += "setlocal\r\n";
bat += "set TEXINPUTS=.;";
bat += texinputs;
bat += ";%TEXINPUTS%\r\n";
}
if (online) {
bat += ipeDir("bin", "ipecurl.exe");
bat += " ";
bat += latex;
bat += "\r\n";
} else {
String path = latexPath();
if (!path.empty()) {
bat += "PATH ";
bat += path;
bat += ";%PATH%\r\n";
}
bat += latex;
bat += " ipetemp.tex\r\n";
}
if (!texinputs.empty())
bat += "endlocal\r\n";
// bat += "pause\r\n"; // alternative for Wine
// CMD.EXE input needs to be encoded in "OEM codepage",
// which can be different from "Windows codepage"
std::wstring wbat = bat.w();
Buffer oemBat(2 * wbat.size() + 1);
CharToOemW(wbat.data(), oemBat.data());
String s = dir + "runlatex.bat";
std::FILE *f = Platform::fopen(s.z(), "wb");
if (!f)
return -1;
std::fputs(oemBat.data(), f);
std::fclose(f);
// Declare and initialize process blocks
PROCESS_INFORMATION processInformation;
STARTUPINFOW startupInfo;
memset(&processInformation, 0, sizeof(processInformation));
memset(&startupInfo, 0, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);
// Call the executable program
String cmd = String("cmd /c call \"") + dir + String("runlatex.bat\"");
std::wstring wcmd = cmd.w();
int result = CreateProcessW(nullptr, wcmd.data(), nullptr, nullptr, FALSE,
NORMAL_PRIORITY_CLASS|CREATE_NO_WINDOW,
nullptr, nullptr, &startupInfo, &processInformation);
if (result == 0)
return -1; // failure to create process
// Wait until child process exits.
WaitForSingleObject(processInformation.hProcess, INFINITE);
// Close process and thread handles.
CloseHandle(processInformation.hProcess);
CloseHandle(processInformation.hThread);
// Apparently WaitForSingleObject doesn't work in Wine
const char *wine = getenv("IPEWINE");
if (wine)
Sleep(Lex(wine).getInt());
return 0;
#else
if (!online && getenv("IPETEXFORMAT")) {
latex = (engine == LatexType::Xetex) ?
"xetex \\&latex" : (engine == LatexType::Luatex) ?
"luatex \\&latex" : "pdftex \\&pdflatex";
}
String s("cd \"");
s += dir;
s += "\"; rm -f ipetemp.log; ";
if (!texinputs.empty()) {
s += "export TEXINPUTS=\"";
s += texinputs;
s += ":$TEXINPUTS\"; ";
}
if (online) {
#ifdef __APPLE__
s += ipeDir("../MacOS", "ipecurl ");
#else
s += "ipecurl ";
#endif
s += latex;
} else {
String path = latexPath();
if (path.empty())
s += latex;
else
s += String("\"") + path + "/" + latex + "\"";
s += " ipetemp.tex";
}
s += " > /dev/null";
int result = std::system(s.z());
if (result != -1)
result = WEXITSTATUS(result);
return result;
#endif
}
#ifdef WIN32
FILE *Platform::fopen(const char *fname, const char *mode)
{
return _wfopen(String(fname).w().data(), String(mode).w().data());
}
int Platform::mkdir(const char *dname)
{
return _wmkdir(String(dname).w().data());
}
//! Return a wide string including a terminating zero character.
std::wstring String::w() const noexcept
{
if (empty())
return L"\0";
int rw = MultiByteToWideChar(CP_UTF8, 0, data(), size(), nullptr, 0);
std::wstring result(rw + 1, wchar_t(0));
MultiByteToWideChar(CP_UTF8, 0, data(), size(), &result[0], rw);
return result;
}
String::String(const wchar_t *wbuf)
{
if (!wbuf) {
iImp = emptyString();
} else {
int rm = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, nullptr, 0, nullptr, nullptr);
iImp = new Imp;
iImp->iRefCount = 1;
iImp->iSize = rm - 1; // rm includes the trailing zero
iImp->iCapacity = (rm + 32) & ~15;
iImp->iData = new char[iImp->iCapacity];
WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, iImp->iData, rm, nullptr, nullptr);
}
}
#endif
// --------------------------------------------------------------------
double Platform::toDouble(String s)
{
#ifdef WIN32
if (p_create_locale != nullptr)
return _strtod_l(s.z(), nullptr, ipeLocale);
else
return strtod(s.z(), nullptr);
#else
return strtod_l(s.z(), nullptr, ipeLocale);
#endif
}
int Platform::toNumber(String s, int &iValue, double &dValue)
{
char *fin = const_cast(s.z());
iValue = std::strtol(s.z(), &fin, 10);
while (*fin == ' ' || *fin == '\t')
++fin;
if (*fin == '\0')
return 1; // integer
#ifdef WIN32
if (p_create_locale != nullptr)
dValue = _strtod_l(s.z(), &fin, ipeLocale);
else
dValue = strtod(s.z(), &fin);
#else
dValue = strtod_l(s.z(), &fin, ipeLocale);
#endif
while (*fin == ' ' || *fin == '\t')
++fin;
if (*fin == '\0')
return 2; // double
// error
return 0;
}
void ipeAssertionFailed(const char *file, int line, const char *assertion)
{
fprintf(stderr, "Assertion failed on line #%d (%s): '%s'\n",
line, file, assertion);
abort();
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipepdfwriter.cpp 0000644 0001750 0001750 00000103700 13561570220 017500 0 ustar otfried otfried // --------------------------------------------------------------------
// Creating PDF output
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipeimage.h"
#include "ipetext.h"
#include "ipepainter.h"
#include "ipegroup.h"
#include "ipereference.h"
#include "ipeutils.h"
#include "ipepdfwriter.h"
#include "ipepdfparser.h"
#include "iperesources.h"
using namespace ipe;
typedef std::vector::const_iterator BmIter;
// --------------------------------------------------------------------
PdfPainter::PdfPainter(const Cascade *style, Stream &stream)
: Painter(style), iStream(stream)
{
State state;
state.iStroke = Color(0,0,0);
state.iFill = Color(0,0,0);
state.iPen = Fixed(1);
state.iDashStyle = "[]0";
state.iLineCap = style->lineCap();
state.iLineJoin = style->lineJoin();
state.iOpacity = Fixed(1);
state.iStrokeOpacity = Fixed(1);
iStream << state.iLineCap - 1 << " J "
<< state.iLineJoin - 1 << " j\n";
iActiveState.push_back(state);
}
void PdfPainter::doNewPath()
{
drawAttributes();
}
void PdfPainter::doMoveTo(const Vector &v)
{
iStream << v << " m\n";
}
void PdfPainter::doLineTo(const Vector &v)
{
iStream << v << " l\n";
}
void PdfPainter::doCurveTo(const Vector &v1, const Vector &v2,
const Vector &v3)
{
iStream << v1 << " " << v2 << " " << v3 << " c\n";
}
void PdfPainter::doClosePath()
{
iStream << "h ";
}
void PdfPainter::doAddClipPath()
{
iStream << "W* n ";
}
void PdfPainter::doPush()
{
State state = iActiveState.back();
iActiveState.push_back(state);
iStream << "q ";
}
void PdfPainter::doPop()
{
iActiveState.pop_back();
iStream << "Q\n";
}
void PdfPainter::drawColor(Stream &stream, Color color,
const char *gray, const char *rgb)
{
if (color.isGray())
stream << color.iRed << " " << gray << "\n";
else
stream << color << " " << rgb << "\n";
}
void PdfPainter::drawAttributes()
{
State &s = iState.back();
State &sa = iActiveState.back();
if (s.iDashStyle != sa.iDashStyle) {
sa.iDashStyle = s.iDashStyle;
iStream << s.iDashStyle << " d\n";
}
if (s.iPen != sa.iPen) {
sa.iPen = s.iPen;
iStream << s.iPen << " w\n";
}
if (s.iLineCap != sa.iLineCap) {
sa.iLineCap = s.iLineCap;
iStream << s.iLineCap - 1 << " J\n";
}
if (s.iLineJoin != sa.iLineJoin) {
sa.iLineJoin = s.iLineJoin;
iStream << s.iLineJoin - 1 << " j\n";
}
if (s.iStroke != sa.iStroke) {
sa.iStroke = s.iStroke;
drawColor(iStream, s.iStroke, "G", "RG");
}
if (s.iFill != sa.iFill || !s.iTiling.isNormal()) {
sa.iFill = s.iFill;
if (!s.iTiling.isNormal()) {
iStream << "/PCS cs\n";
s.iFill.saveRGB(iStream);
iStream << " /Pat" << s.iTiling.index() << " scn\n";
} else
drawColor(iStream, s.iFill, "g", "rg");
}
drawOpacity(true);
}
static const char *opacityName(Fixed alpha)
{
static char buf[12];
sprintf(buf, "/alpha%03d", alpha.internal());
return buf;
}
void PdfPainter::drawOpacity(bool withStroke)
{
State &s = iState.back();
State &sa = iActiveState.back();
if (s.iOpacity != sa.iOpacity) {
sa.iOpacity = s.iOpacity;
sa.iStrokeOpacity = s.iOpacity;
iStream << opacityName(s.iOpacity) << " gs\n";
}
if (withStroke && s.iStrokeOpacity != sa.iStrokeOpacity) {
iStream << opacityName(s.iStrokeOpacity) << "s gs\n";
}
}
// If gradient fill is set, then StrokeAndFill does NOT stroke!
void PdfPainter::doDrawPath(TPathMode mode)
{
bool eofill = (fillRule() == EEvenOddRule);
// if (!mode)
// iStream << "n\n"; // no op path
const Gradient *g = nullptr;
Attribute grad = iState.back().iGradient;
if (!grad.isNormal())
g = iCascade->findGradient(grad);
if (g) {
if (mode == EStrokedOnly)
iStream << "S\n";
else
iStream << (eofill ? "q W* n " : "q W n ")
<< matrix() * g->iMatrix << " cm /Grad" << grad.index()
<< " sh Q\n";
} else {
if (mode == EFilledOnly)
iStream << (eofill ? "f*\n" : "f\n");
else if (mode == EStrokedOnly)
iStream << "S\n";
else
iStream << (eofill ? "B*\n" : "B\n"); // fill and then stroke
}
}
void PdfPainter::doDrawBitmap(Bitmap bitmap)
{
if (bitmap.objNum() < 0)
return;
drawOpacity(false);
iStream << matrix() << " cm /Image" << bitmap.objNum() << " Do\n";
}
void PdfPainter::doDrawText(const Text *text)
{
const Text::XForm *xf = text->getXForm();
if (!xf)
return;
drawOpacity(false);
pushMatrix();
transform(Matrix(xf->iStretch, 0, 0, xf->iStretch, 0, 0));
translate(xf->iTranslation);
iStream << matrix() << " cm " ;
iStream << "/" << xf->iName << " Do\n";
popMatrix();
}
void PdfPainter::doDrawSymbol(Attribute symbol)
{
const Symbol *sym = cascade()->findSymbol(symbol);
if (!sym)
return;
if (sym->iXForm)
iStream << "/Symbol" << symbol.index() << " Do\n";
else
sym->iObject->draw(*this);
}
// --------------------------------------------------------------------
/*! \class ipe::PdfWriter
\brief Create PDF file.
This class is responsible for the creation of a PDF file from the
Ipe data. You have to create a PdfWriter first, providing a file
that has been opened for (binary) writing and is empty. Then call
createPages() to embed the pages. Optionally, call \c
createXmlStream to embed a stream with the XML representation of the
document. Finally, call \c createTrailer to complete the PDF
document, and close the file.
Some reserved PDF object numbers:
- 0: Must be left empty (a PDF restriction).
- 1: XML stream.
- 2: Parent of all pages objects.
- 3: ExtGState resource from pdflatex
- 4: Shading resource from pdflatex
- 5: Pattern resource from pdflatex
- 6: ColorSpace resource from pdflatex
*/
//! Create a PDF writer operating on this (open and empty) file.
PdfWriter::PdfWriter(TellStream &stream, const Document *doc, const PdfResources *resources,
uint32_t flags, int fromPage, int toPage,
int compression)
: iStream(stream), iDoc(doc), iResources(resources), iSaveFlags(flags),
iFromPage(fromPage), iToPage(toPage)
{
iCompressLevel = compression;
iObjNum = 7; // 0 - 6 are reserved
iXmlStreamNum = -1; // no XML stream yet
iExtGState = -1;
iPatternNum = -1;
iBookmarks = -1;
iDests = -1;
if (iFromPage < 0 || iFromPage >= iDoc->countPages())
iFromPage = 0;
if (iToPage < iFromPage || iToPage >= iDoc->countPages())
iToPage = iDoc->countPages() - 1;
// mark all bitmaps as not embedded
BitmapFinder bm;
iDoc->findBitmaps(bm);
int id = -1;
for (std::vector::iterator it = bm.iBitmaps.begin();
it != bm.iBitmaps.end(); ++it) {
it->setObjNum(id);
--id;
}
iStream << "%PDF-1.4\n";
// embed all fonts and other resources from Pdflatex
embedResources();
// embed resources from pdflatex
embedLatexResource(3, "ExtGState");
embedLatexResource(4, "Shading");
embedLatexResource(5, "Pattern");
embedLatexResource(6, "ColorSpace");
// embed all extgstate objects
AttributeSeq os;
iDoc->cascade()->allNames(EOpacity, os);
if (os.size() > 0) {
iExtGState = startObject();
iStream << "<<\n";
for (const auto & obj : os) {
Attribute alpha = iDoc->cascade()->find(EOpacity, obj);
assert(alpha.isNumber());
iStream << opacityName(alpha.number()) << " << /CA " << alpha.number()
<< " /ca " << alpha.number() << " >>\n";
iStream << opacityName(alpha.number()) << "s << /CA " << alpha.number()
<< " >>\n";
}
iStream << ">> endobj\n";
}
// embed all gradients
AttributeSeq gs;
iDoc->cascade()->allNames(EGradient, gs);
for (const auto & grad : gs) {
const Gradient *g = iDoc->cascade()->findGradient(grad);
int num = startObject();
iStream << "<<\n"
<< " /ShadingType " << int(g->iType) << "\n"
<< " /ColorSpace /DeviceRGB\n";
if (g->iType == Gradient::EAxial)
iStream << " /Coords [" << g->iV[0] << " " << g->iV[1] << "]\n";
else
iStream << " /Coords [" << g->iV[0] << " " << g->iRadius[0]
<< " " << g->iV[1] << " " << g->iRadius[1] << "]\n";
iStream << " /Extend [" << (g->iExtend ? "true true]\n" : "false false]\n");
if (g->iStops.size() == 2) {
iStream << " /Function << /FunctionType 2 /Domain [ 0 1 ] /N 1\n"
<< " /C0 [";
g->iStops[0].color.saveRGB(iStream);
iStream << "]\n" << " /C1 [";
g->iStops[1].color.saveRGB(iStream);
iStream << "] >>\n";
} else {
// need to stitch
iStream << " /Function <<\n"
<< " /FunctionType 3 /Domain [ 0 1 ]\n"
<< " /Bounds [";
int count = 0;
for (int i = 1; i < size(g->iStops) - 1; ++i) {
if (g->iStops[i].offset > g->iStops[i-1].offset) {
iStream << g->iStops[i].offset << " ";
++count;
}
}
iStream << "]\n /Encode [";
for (int i = 0; i <= count; ++i)
iStream << "0.0 1.0 ";
iStream << "]\n /Functions [\n";
for (int i = 1; i < size(g->iStops); ++i) {
if (g->iStops[i].offset > g->iStops[i-1].offset) {
iStream << " << /FunctionType 2 /Domain [ 0 1 ] /N 1 /C0 [";
g->iStops[i-1].color.saveRGB(iStream);
iStream << "] /C1 [";
g->iStops[i].color.saveRGB(iStream);
iStream << "] >>\n";
}
}
iStream << "] >>\n";
}
iStream << ">> endobj\n";
iGradients[grad.index()] = num;
}
// embed all tilings
AttributeSeq ts;
std::map patterns;
iDoc->cascade()->allNames(ETiling, ts);
if (ts.size() > 0) {
for (const auto & tiling : ts) {
const Tiling *t = iDoc->cascade()->findTiling(tiling);
Linear m(t->iAngle);
int num = startObject();
iStream << "<<\n"
<< "/Type /Pattern\n"
<< "/PatternType 1\n" // tiling pattern
<< "/PaintType 2\n" // uncolored pattern
<< "/TilingType 2\n" // faster
<< "/BBox [ 0 0 100 " << t->iStep << " ]\n"
<< "/XStep 100\n"
<< "/YStep " << t->iStep << "\n"
<< "/Resources << >>\n"
<< "/Matrix [" << m << " 0 0]\n";
String s;
StringStream ss(s);
ss << "0 0 100 " << t->iWidth << " re f\n";
createStream(s.data(), s.size(), false);
patterns[tiling.index()] = num;
}
// create pattern dictionary
iPatternNum = startObject();
iStream << "<<\n";
for (const auto & pattern : ts) {
iStream << "/Pat" << pattern.index() << " "
<< patterns[pattern.index()] << " 0 R\n";
}
iStream << ">> endobj\n";
}
// embed all symbols with xform attribute
AttributeSeq sys;
iDoc->cascade()->allNames(ESymbol, sys);
if (sys.size()) {
for (const auto & sysi : sys) {
const Symbol *sym = iDoc->cascade()->findSymbol(sysi);
if (sym->iXForm) {
// compute bbox for object
BBoxPainter bboxPainter(iDoc->cascade());
sym->iObject->draw(bboxPainter);
Rect bbox = bboxPainter.bbox();
// embed all bitmaps it uses
BitmapFinder bm;
sym->iObject->accept(bm);
embedBitmaps(bm);
int num = startObject();
iStream << "<<\n";
iStream << "/Type /XObject\n";
iStream << "/Subtype /Form\n";
iStream << "/BBox [" << bbox << "]\n";
createResources(bm);
String s;
StringStream ss(s);
PdfPainter painter(iDoc->cascade(), ss);
sym->iObject->draw(painter);
createStream(s.data(), s.size(), false);
iSymbols[sysi.index()] = num;
}
}
}
}
//! Destructor.
PdfWriter::~PdfWriter()
{
// nothing
}
/*! Write the beginning of the next object: "no 0 obj " and save
information about file position. Default argument uses next unused
object number. Returns number of new object. */
int PdfWriter::startObject(int objnum)
{
if (objnum < 0)
objnum = iObjNum++;
iXref[objnum] = iStream.tell();
iStream << objnum << " 0 obj ";
return objnum;
}
bool PdfWriter::hasResource(String kind) const noexcept
{
return iResources && iResources->resourcesOfKind(kind);
}
/*! Write all PDF resources to the PDF file, and record their object
numbers. Embeds nothing if \c resources is nullptr, but must be
called nevertheless. */
void PdfWriter::embedResources()
{
bool inflate = (iCompressLevel == 0);
if (iResources) {
const std::vector &seq = iResources->embedSequence();
for (auto &num : seq) {
const PdfObj *obj = iResources->object(num);
int embedNum = startObject();
if (obj->dict() && obj->dict()->get("IpeId", nullptr))
embedIpeXForm(obj->dict());
else
obj->write(iStream, &iResourceNumber, inflate);
iStream << " endobj\n";
iResourceNumber[num] = embedNum;
}
}
}
void PdfWriter::embedIpeXForm(const PdfDict *d)
{
bool inflate = (iCompressLevel == 0) && d->deflated();
iStream << "<<";
for (int i = 0; i < d->count(); ++i) {
String key = d->key(i);
// skip /IpeId etc keys
if (key.left(3) == "Ipe")
continue;
if ((inflate && key == "Filter") || key == "Length")
continue; // remove /FlateDecode filter and /Length
iStream << "/" << key << " ";
if (key == "Resources") {
const PdfObj *res = d->value(i);
if (res->ref())
res = iResources->object(res->ref()->value());
if (res->dict())
embedXFormResource(res->dict());
else // should not happen!
d->value(i)->write(iStream, &iResourceNumber);
} else if (key == "BBox") {
const TextPadding *pad = iDoc->cascade()->findTextPadding();
std::vector bbox;
d->getNumberArray("BBox", nullptr, bbox);
if (pad && bbox.size() == 4) {
bbox[0] -= pad->iLeft;
bbox[1] -= pad->iBottom;
bbox[2] += pad->iRight;
bbox[3] += pad->iTop;
}
iStream << "[";
for (auto it = bbox.begin(); it != bbox.end(); ++it)
iStream << *it << " ";
iStream << "]";
} else
d->value(i)->write(iStream, &iResourceNumber);
iStream << " ";
}
Buffer stream = inflate ? d->inflate() : d->stream();
if (stream.size() > 0) {
iStream << "/Length " << stream.size()
<< ">>\nstream\n";
for (int i = 0; i < stream.size(); ++i)
iStream.putChar(stream[i]);
iStream << "\nendstream";
} else
iStream << ">>";
}
void PdfWriter::embedXFormResource(const PdfDict *d)
{
iStream << "<<";
for (int i = 0; i < d->count(); ++i) {
String key = d->key(i);
iStream << "/" << key << " ";
if (key == "ColorSpace" || key == "Shading" || key == "Pattern" ||
key == "ExtGState") {
ipeDebug("PDF Writer: Conflicting resource in XForm: %s", key.z());
} else
d->value(i)->write(iStream, &iResourceNumber);
}
if (hasResource("ExtGState"))
iStream << "/ExtGState 3 0 R\n";
if (hasResource("Shading"))
iStream << "/ColorSpace 4 0 R\n";
if (hasResource("Pattern"))
iStream << "/Pattern 5 0 R\n";
if (hasResource("ColorSpace"))
iStream << "/ColorSpace 6 0 R\n";
iStream << ">>";
}
void PdfWriter::embedLatexResource(int num, String kind)
{
if (hasResource(kind)) {
startObject(num);
iStream << "<<\n";
embedResource(kind);
iStream << ">> endobj\n";
}
}
void PdfWriter::embedResource(String kind)
{
if (!iResources)
return;
const PdfDict *d = iResources->resourcesOfKind(kind);
if (!d)
return;
for (int i = 0; i < d->count(); ++i) {
iStream << "/" << d->key(i) << " ";
d->value(i)->write(iStream, &iResourceNumber);
iStream << " ";
}
}
//! Write a stream.
/*! Write a stream, either plain or compressed, depending on compress
level. Object must have been created with dictionary start having
been written.
If \a preCompressed is true, the data is already deflated.
*/
void PdfWriter::createStream(const char *data, int size, bool preCompressed)
{
if (preCompressed) {
iStream << "/Length " << size << " /Filter /FlateDecode >>\nstream\n";
iStream.putRaw(data, size);
iStream << "\nendstream endobj\n";
return;
}
if (iCompressLevel > 0) {
int deflatedSize;
Buffer deflated = DeflateStream::deflate(data, size, deflatedSize,
iCompressLevel);
iStream << "/Length " << deflatedSize
<< " /Filter /FlateDecode >>\nstream\n";
iStream.putRaw(deflated.data(), deflatedSize);
iStream << "\nendstream endobj\n";
} else {
iStream << "/Length " << size << " >>\nstream\n";
iStream.putRaw(data, size);
iStream << "endstream endobj\n";
}
}
// --------------------------------------------------------------------
void PdfWriter::embedBitmap(Bitmap bitmap)
{
int smaskNum = -1;
auto embed = bitmap.embed();
if (bitmap.hasAlpha() && embed.second.size() > 0) {
smaskNum = startObject();
iStream << "<<\n";
iStream << "/Type /XObject\n";
iStream << "/Subtype /Image\n";
iStream << "/Width " << bitmap.width() << "\n";
iStream << "/Height " << bitmap.height() << "\n";
iStream << "/ColorSpace /DeviceGray\n";
iStream << "/Filter /FlateDecode\n";
iStream << "/BitsPerComponent 8\n";
iStream << "/Length " << embed.second.size() << "\n>> stream\n";
iStream.putRaw(embed.second.data(), embed.second.size());
iStream << "\nendstream endobj\n";
}
int objnum = startObject();
iStream << "<<\n";
iStream << "/Type /XObject\n";
iStream << "/Subtype /Image\n";
iStream << "/Width " << bitmap.width() << "\n";
iStream << "/Height " << bitmap.height() << "\n";
if (bitmap.isGray())
iStream << "/ColorSpace /DeviceGray\n";
else
iStream << "/ColorSpace /DeviceRGB\n";
if (bitmap.isJpeg())
iStream << "/Filter /DCTDecode\n";
else
iStream << "/Filter /FlateDecode\n";
iStream << "/BitsPerComponent 8\n";
if (smaskNum >= 0) {
iStream << "/SMask " << smaskNum << " 0 R\n";
} else if (bitmap.colorKey() >= 0) {
int r = (bitmap.colorKey() >> 16) & 0xff;
int g = (bitmap.colorKey() >> 8) & 0xff;
int b = bitmap.colorKey() & 0xff;
iStream << "/Mask [" << r << " " << r;
if (!bitmap.isGray())
iStream << " " << g << " " << g << " " << b << " " << b;
iStream << "]\n";
}
iStream << "/Length " << embed.first.size() << "\n>> stream\n";
iStream.putRaw(embed.first.data(), embed.first.size());
iStream << "\nendstream endobj\n";
bitmap.setObjNum(objnum);
}
void PdfWriter::embedBitmaps(const BitmapFinder &bm)
{
for (BmIter it = bm.iBitmaps.begin(); it != bm.iBitmaps.end(); ++it) {
BmIter it1 = std::find(iBitmaps.begin(), iBitmaps.end(), *it);
if (it1 == iBitmaps.end()) {
// look again, more carefully
for (it1 = iBitmaps.begin();
it1 != iBitmaps.end() && !it1->equal(*it); ++it1)
;
if (it1 == iBitmaps.end())
embedBitmap(*it); // not yet embedded
else
it->setObjNum(it1->objNum()); // identical Bitmap is embedded
iBitmaps.push_back(*it);
}
}
}
void PdfWriter::createResources(const BitmapFinder &bm)
{
// These are only the resources needed by Ipe drawing directly.
// Resources used from inside text objects (e.g. by tikz)
// are stored inside the XObject resources.
// Font is only used inside XObject, no font resource needed on page
// Resources:
// ProcSet, ExtGState, ColorSpace, Pattern, Shading, XObject, Font
// not used now: Properties
// ProcSet
iStream << "/Resources <<\n /ProcSet [/PDF";
if (iResources)
iStream << "/Text";
if (!bm.iBitmaps.empty())
iStream << "/ImageB/ImageC";
iStream << "]\n";
// Shading
if (iGradients.size()) {
iStream << " /Shading <<";
for (std::map::const_iterator it = iGradients.begin();
it != iGradients.end(); ++it)
iStream << " /Grad" << it->first << " " << it->second << " 0 R";
iStream << " >>\n";
}
// ExtGState
if (iExtGState >= 0)
iStream << " /ExtGState " << iExtGState << " 0 R\n";
// ColorSpace
if (iPatternNum >= 0) {
iStream << " /ColorSpace << /PCS [/Pattern /DeviceRGB] ";
iStream << ">>\n";
}
// Pattern
if (iPatternNum >= 0)
iStream << " /Pattern " << iPatternNum << " 0 R\n";
// XObject
// TODO: Is "hasResource" here used correctly?
// From Tikz xobject resources seem to go into the Ipe xform.
if (!bm.iBitmaps.empty() || !iSymbols.empty() || hasResource("XObject")) {
iStream << " /XObject << ";
for (BmIter it = bm.iBitmaps.begin(); it != bm.iBitmaps.end(); ++it) {
// mention each PDF object only once
BmIter it1;
for (it1 = bm.iBitmaps.begin();
it1 != it && it1->objNum() != it->objNum(); it1++)
;
if (it1 == it)
iStream << "/Image" << it->objNum() << " " << it->objNum() << " 0 R ";
}
for (std::map::const_iterator it = iSymbols.begin();
it != iSymbols.end(); ++it)
iStream << "/Symbol" << it->first << " " << it->second << " 0 R ";
embedResource("XObject");
iStream << ">>\n";
}
iStream << " >>\n";
}
// --------------------------------------------------------------------
void PdfWriter::paintView(Stream &stream, int pno, int view)
{
const Page *page = iDoc->page(pno);
PdfPainter painter(iDoc->cascade(), stream);
const Symbol *background =
iDoc->cascade()->findSymbol(Attribute::BACKGROUND());
if (background && page->findLayer("BACKGROUND") < 0)
painter.drawSymbol(Attribute::BACKGROUND());
if (iDoc->properties().iNumberPages && iResources) {
const Text *pn = iResources->pageNumber(pno, view);
if (pn)
pn->draw(painter);
}
const Text *title = page->titleText();
if (title)
title->draw(painter);
for (int i = 0; i < page->count(); ++i) {
if (page->objectVisible(view, i))
page->object(i)->draw(painter);
}
}
//! create contents and page stream for this page view.
void PdfWriter::createPageView(int pno, int view)
{
const Page *page = iDoc->page(pno);
// Find bitmaps to embed
BitmapFinder bm;
const Symbol *background =
iDoc->cascade()->findSymbol(Attribute::BACKGROUND());
if (background && page->findLayer("BACKGROUND") < 0)
background->iObject->accept(bm);
bm.scanPage(page);
// ipeDebug("# of bitmaps: %d", bm.iBitmaps.size());
embedBitmaps(bm);
// create page stream
String pagedata;
StringStream sstream(pagedata);
if (iCompressLevel > 0) {
DeflateStream dfStream(sstream, iCompressLevel);
paintView(dfStream, pno, view);
dfStream.close();
} else
paintView(sstream, pno, view);
int firstLink = -1;
int lastLink = -1;
for (int i = 0; i < page->count(); ++i) {
const Group *g = page->object(i)->asGroup();
if (g && page->objectVisible(view, i) && !g->url().empty()) {
lastLink = startObject();
if (firstLink < 0) firstLink = lastLink;
iStream << "<<\n"
<< "/Type /Annot\n"
<< "/Subtype /Link\n"
<< "/H /N\n"
<< "/Rect [" << page->bbox(i) << "]\n"
<< "/A <url();
if (url.left(6) == "named:") {
iStream << "/Named/N/" << url.substr(6);
} else {
if (url.left(7) == "launch:") {
url = url.substr(7);
iStream << "/Launch/F";
} else if (url.left(5) == "goto:") {
url = url.substr(5);
iStream << "/GoTo/D";
} else
iStream << "/URI/URI";
writeString(url);
}
iStream << ">>\n>> endobj\n";
}
}
int notesObj = -1;
if (!page->notes().empty() &&
(!(iSaveFlags & SaveFlag::Export) || (iSaveFlags & SaveFlag::KeepNotes))) {
notesObj = startObject();
iStream << "<<\n"
<< "/Type /Annot\n"
<< "/Subtype /Text\n"
<< "/Rect [20 40 30 40]\n"
<< "/F 4\n"
<< "/Contents ";
writeString(page->notes());
iStream << "\n>> endobj\n";
}
int contentsobj = startObject();
iStream << "<<\n";
createStream(pagedata.data(), pagedata.size(), (iCompressLevel > 0));
int pageobj = startObject();
iStream << "<<\n";
iStream << "/Type /Page\n";
if (firstLink >= 0 || notesObj >= 0) {
iStream << "/Annots [ ";
if (firstLink >= 0) {
while (firstLink <= lastLink)
iStream << firstLink++ << " 0 R ";
}
if (notesObj >= 0)
iStream << notesObj << " 0 R";
iStream << "]\n";
}
iStream << "/Contents " << contentsobj << " 0 R\n";
// iStream << "/Rotate 0\n";
createResources(bm);
if (!page->effect(view).isNormal()) {
const Effect *effect = iDoc->cascade()->findEffect(page->effect(view));
if (effect)
effect->pageDictionary(iStream);
}
const Layout *layout = iDoc->cascade()->findLayout();
iStream << "/MediaBox [ " << layout->paper() << "]\n";
int viewBBoxLayer = page->findLayer("VIEWBBOX");
Rect bbox;
if (viewBBoxLayer >= 0 && page->visible(view, viewBBoxLayer))
bbox = page->viewBBox(iDoc->cascade(), view);
else
bbox = page->pageBBox(iDoc->cascade());
if (layout->iCrop && !bbox.isEmpty())
iStream << "/CropBox [" << bbox << "]\n";
if (!bbox.isEmpty())
iStream << "/ArtBox [" << bbox << "]\n";
iStream << "/Parent 2 0 R\n";
iStream << ">> endobj\n";
iPageObjectNumbers.push_back({ pno, view, pageobj });
}
//! Create all PDF pages.
void PdfWriter::createPages()
{
for (int page = iFromPage; page <= iToPage; ++page) {
if ((iSaveFlags & SaveFlag::MarkedView) && !iDoc->page(page)->marked())
continue;
int nViews = iDoc->page(page)->countViews();
if (iSaveFlags & SaveFlag::MarkedView) {
bool shown = false;
for (int view = 0; view < nViews; ++view) {
if (iDoc->page(page)->markedView(view)) {
createPageView(page, view);
shown = true;
}
}
if (!shown)
createPageView(page, nViews - 1);
} else {
for (int view = 0; view < nViews; ++view)
createPageView(page, view);
}
}
}
//! Create a stream containing the XML data.
void PdfWriter::createXmlStream(String xmldata, bool preCompressed)
{
iXmlStreamNum = startObject(1);
iStream << "<<\n/Type /Ipe\n";
createStream(xmldata.data(), xmldata.size(), preCompressed);
}
//! Write a PDF string object to the PDF stream.
void PdfWriter::writeString(String text)
{
// Check if it is all ASCII
bool isAscii = true;
for (int i = 0; isAscii && i < text.size(); ++i) {
if (text[i] & 0x80)
isAscii = false;
}
if (isAscii) {
iStream << "(";
for (int i = 0; i < text.size(); ++i) {
char ch = text[i];
switch (ch) {
case '(':
case ')':
case '\\':
iStream << "\\";
// fall through
default:
iStream << ch;
break;
}
}
iStream << ")";
} else {
char buf[5];
iStream << "";
}
}
// --------------------------------------------------------------------
int PdfWriter::pageObjectNumber(int page, int view)
{
auto it = std::find_if(iPageObjectNumbers.begin(), iPageObjectNumbers.end(),
[=](const PON &pon) {return pon.page == page && pon.view == view;});
if (it != iPageObjectNumbers.end())
return it->objNum;
ipeDebug("pageObjectNumber not found, this is a bug!");
return 0;
}
struct Section {
int iPage;
int iObjNum;
std::vector iSubPages;
};
//! Create the bookmarks (PDF outline).
void PdfWriter::createBookmarks()
{
// first collect all information
std::vector sections;
for (int pg = iFromPage; pg <= iToPage; ++pg) {
if ((iSaveFlags & SaveFlag::MarkedView) && !iDoc->page(pg)->marked())
continue;
String s = iDoc->page(pg)->section(0);
String ss = iDoc->page(pg)->section(1);
if (!s.empty()) {
Section sec;
sec.iPage = pg;
sections.push_back(sec);
}
if (!sections.empty() && !ss.empty())
sections.back().iSubPages.push_back(pg);
}
if (sections.empty())
return;
// reserve outline object
iBookmarks = iObjNum++;
// assign object numbers
for (int s = 0; s < size(sections); ++s) {
sections[s].iObjNum = iObjNum++;
iObjNum += sections[s].iSubPages.size(); // leave space for subsections
}
// embed root
startObject(iBookmarks);
iStream << "<<\n/First " << sections[0].iObjNum << " 0 R\n"
<< "/Count " << int(sections.size()) << "\n"
<< "/Last " << sections.back().iObjNum << " 0 R\n>> endobj\n";
for (int s = 0; s < size(sections); ++s) {
int count = sections[s].iSubPages.size();
int obj = sections[s].iObjNum;
// embed section
startObject(obj);
iStream << "<<\n/Title ";
writeString(iDoc->page(sections[s].iPage)->section(0));
iStream << "\n/Parent " << iBookmarks << " 0 R\n"
<< "/Dest [ " << pageObjectNumber(sections[s].iPage, 0)
<< " 0 R /XYZ null null null ]\n";
if (s > 0)
iStream << "/Prev " << sections[s-1].iObjNum << " 0 R\n";
if (s < size(sections) - 1)
iStream << "/Next " << sections[s+1].iObjNum << " 0 R\n";
if (count > 0)
iStream << "/Count " << -count << "\n"
<< "/First " << (obj + 1) << " 0 R\n"
<< "/Last " << (obj + count) << " 0 R\n";
iStream << ">> endobj\n";
// using ids obj + 1 .. obj + count for the subsections
for (int ss = 0; ss < count; ++ss) {
int pageNo = sections[s].iSubPages[ss];
startObject(obj + ss + 1);
iStream << "<<\n/Title ";
writeString(iDoc->page(pageNo)->section(1));
iStream << "\n/Parent " << obj << " 0 R\n"
<< "/Dest [ " << pageObjectNumber(pageNo, 0)
<< " 0 R /XYZ null null null ]\n";
if (ss > 0)
iStream << "/Prev " << (obj + ss) << " 0 R\n";
if (ss < count - 1)
iStream << "/Next " << (obj + ss + 2) << " 0 R\n";
iStream << ">> endobj\n";
}
}
}
//! Create the named destinations.
void PdfWriter::createNamedDests()
{
std::vector> dests;
for (int pg = iFromPage; pg <= iToPage; ++pg) {
if ((iSaveFlags & SaveFlag::MarkedView) && !iDoc->page(pg)->marked())
continue;
String s = iDoc->page(pg)->section(0);
if (!s.empty())
dests.push_back(std::make_pair(s, pageObjectNumber(pg, 0)));
}
if (dests.empty())
return;
std::sort(dests.begin(), dests.end());
iDests = startObject();
iStream << "<<\n/Limits [";
writeString(dests.front().first);
iStream << " ";
writeString(dests.back().first);
iStream << "]\n/Names [\n";
for (const auto & dest : dests) {
writeString(dest.first);
iStream << " [" << dest.second << " 0 R /XYZ null null null]\n";
}
iStream << "]>> endobj\n";
}
// --------------------------------------------------------------------
//! Create the root objects and trailer of the PDF file.
void PdfWriter::createTrailer()
{
const Document::SProperties &props = iDoc->properties();
// create /Pages
startObject(2);
iStream << "<<\n" << "/Type /Pages\n";
iStream << "/Count " << int(iPageObjectNumbers.size()) << "\n";
iStream << "/Kids [ ";
for (auto pon : iPageObjectNumbers)
iStream << pon.objNum << " 0 R ";
iStream << "]\n>> endobj\n";
// create Name dictionary
int nameDict = -1;
if (iDests >= 0) {
nameDict = startObject();
iStream << "<> endobj\n";
}
// create PieceInfo
int pieceInfo = startObject();
iStream << "<
> >> endobj\n";
// create /Catalog
int catalogobj = startObject();
iStream << "<<\n/Type /Catalog\n/Pages 2 0 R\n"
<< "/PieceInfo " << pieceInfo << " 0 R\n";
if (props.iFullScreen)
iStream << "/PageMode /FullScreen\n";
if (iBookmarks >= 0) {
if (!props.iFullScreen)
iStream << "/PageMode /UseOutlines\n";
iStream << "/Outlines " << iBookmarks << " 0 R\n";
}
if (nameDict >= 0)
iStream << "/Names " << nameDict << " 0 R\n";
if (iDoc->countTotalViews() > 1) {
iStream << "/PageLabels << /Nums [ ";
int count = 0;
for (int page = 0; page < iDoc->countPages(); ++page) {
if (!(iSaveFlags & SaveFlag::MarkedView) || iDoc->page(page)->marked()) {
int nviews = (iSaveFlags & SaveFlag::MarkedView) ? iDoc->page(page)->countMarkedViews() :
iDoc->page(page)->countViews();
if (nviews > 1) {
iStream << count << " <>";
} else { // one view only!
iStream << count << " <
>";
}
count += nviews;
}
}
iStream << "] >>\n";
}
iStream << ">> endobj\n";
// create /Info
int infoobj = startObject();
iStream << "<<\n";
if (!props.iCreator.empty()) {
iStream << "/Creator (" << props.iCreator << ")\n";
iStream << "/Producer (" << props.iCreator << ")\n";
}
if (!props.iTitle.empty()) {
iStream << "/Title ";
writeString(props.iTitle);
iStream << "\n";
}
if (!props.iAuthor.empty()) {
iStream << "/Author ";
writeString(props.iAuthor);
iStream << "\n";
}
if (!props.iSubject.empty()) {
iStream << "/Subject ";
writeString(props.iSubject);
iStream << "\n";
}
if (!props.iKeywords.empty()) {
iStream << "/Keywords ";
writeString(props.iKeywords);
iStream << "\n";
}
iStream << "/CreationDate (" << props.iCreated << ")\n";
iStream << "/ModDate (" << props.iModified << ")\n";
iStream << ">> endobj\n";
// create Xref
long xrefpos = iStream.tell();
iStream << "xref\n0 " << iObjNum << "\n";
for (int obj = 0; obj < iObjNum; ++obj) {
std::map::const_iterator it = iXref.find(obj);
char s[12];
if (it == iXref.end()) {
std::sprintf(s, "%010d", obj);
iStream << s << " 00000 f \n"; // note the final space!
} else {
std::sprintf(s, "%010ld", iXref[obj]);
iStream << s << " 00000 n \n"; // note the final space!
}
}
iStream << "trailer\n<<\n";
iStream << "/Size " << iObjNum << "\n";
iStream << "/Root " << catalogobj << " 0 R\n";
iStream << "/Info " << infoobj << " 0 R\n";
iStream << ">>\nstartxref\n" << int(xrefpos) << "\n%%EOF\n";
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipegroup.cpp 0000644 0001750 0001750 00000024655 13561570220 016641 0 ustar otfried otfried // --------------------------------------------------------------------
// The group object
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipegroup.h"
#include "ipepainter.h"
#include "ipetext.h"
#include "ipeshape.h"
using namespace ipe;
/*! \class ipe::Group
\ingroup obj
\brief The group object.
Ipe objects can be grouped together, and the resulting composite can
be used like any Ipe object.
This is an application of the "Composite" pattern.
*/
//! Create empty group (objects can be added later).
Group::Group() : Object()
{
iImp = new Imp;
iImp->iRefCount = 1;
iImp->iPinned = ENoPin;
iDecoration = Attribute::NORMAL();
}
//! Create empty group with these attributes (objects can be added later).
Group::Group(const XmlAttributes &attr)
: Object(attr)
{
iImp = new Imp;
iImp->iRefCount = 1;
iImp->iPinned = ENoPin;
String str;
if (attr.has("clip", str)) {
Shape clip;
if (clip.load(str))
iClip = clip;
}
iUrl = attr["url"];
iDecoration = attr.has("decoration", str) ?
Attribute(true, str) : Attribute::NORMAL();
}
//! Copy constructor. Constant time --- components are not copied!
Group::Group(const Group &rhs)
: Object(rhs)
{
iImp = rhs.iImp;
iImp->iRefCount++;
iClip = rhs.iClip;
iUrl = rhs.iUrl;
iDecoration = rhs.iDecoration;
}
//! Destructor.
Group::~Group()
{
if (iImp->iRefCount == 1) {
for (List::iterator it = iImp->iObjects.begin();
it != iImp->iObjects.end(); ++it) {
delete *it;
*it = nullptr;
}
delete iImp;
} else
iImp->iRefCount--;
}
//! Assignment operator (constant-time).
Group &Group::operator=(const Group &rhs)
{
if (this != &rhs) {
if (iImp->iRefCount == 1)
delete iImp;
else
iImp->iRefCount--;
iImp = rhs.iImp;
iImp->iRefCount++;
iClip = rhs.iClip;
iUrl = rhs.iUrl;
iDecoration = rhs.iDecoration;
Object::operator=(rhs);
}
return *this;
}
//! Clone a group object (constant-time).
Object *Group::clone() const
{
return new Group(*this);
}
//! Return pointer to this object.
Group *Group::asGroup()
{
return this;
}
//! Return pointer to this object.
const Group *Group::asGroup() const
{
return this;
}
Object::Type Group::type() const
{
return EGroup;
}
//! Add an object.
/*! Takes ownership of the object.
This will panic if the object shares its implementation!
The method is only useful right after construction of the group. */
void Group::push_back(Object *obj)
{
assert(iImp->iRefCount == 1);
iImp->iObjects.push_back(obj);
iImp->iPinned = TPinned(iImp->iPinned | obj->pinned());
}
//! Save all the components, one by one, in XML format.
void Group::saveComponentsAsXml(Stream &stream) const
{
for (const_iterator it = begin(); it != end(); ++it)
(*it)->saveAsXml(stream, String());
}
//! Call visitGroup of visitor.
void Group::accept(Visitor &visitor) const
{
visitor.visitGroup(this);
}
void Group::saveAsXml(Stream &stream, String layer) const
{
stream << "\n";
saveComponentsAsXml(stream);
stream << "\n";
}
// --------------------------------------------------------------------
class DecorationPainter : public Painter {
public:
DecorationPainter(Painter &painter, const Vector ¢er,
double dx, double dy);
protected:
virtual void doPush();
virtual void doPop();
virtual void doNewPath();
virtual void doMoveTo(const Vector &v);
virtual void doLineTo(const Vector &v);
virtual void doCurveTo(const Vector &v1, const Vector &v2,
const Vector &v3);
virtual void doClosePath();
virtual void doDrawPath(TPathMode mode);
Vector adapt(const Vector &v);
private:
Painter &iPainter;
Vector iCenter;
double iX, iY;
};
DecorationPainter::DecorationPainter(Painter &painter, const Vector ¢er,
double dx, double dy) :
Painter(painter.cascade()), iPainter(painter), iCenter(center),
iX(dx), iY(dy)
{
// nothing
}
Vector DecorationPainter::adapt(const Vector &v)
{
Vector r;
r.x = (v.x < iCenter.x) ? v.x - iX : v.x + iX;
r.y = (v.y < iCenter.y) ? v.y - iY : v.y + iY;
return r;
}
void DecorationPainter::doPush()
{
iPainter.push();
}
void DecorationPainter::doPop()
{
iPainter.pop();
}
void DecorationPainter::doNewPath()
{
iPainter.setState(iState.back());
iPainter.newPath();
}
void DecorationPainter::doMoveTo(const Vector &v)
{
iPainter.moveTo(adapt(v));
}
void DecorationPainter::doLineTo(const Vector &v)
{
iPainter.lineTo(adapt(v));
}
void DecorationPainter::doCurveTo(const Vector &v1, const Vector &v2,
const Vector &v3)
{
iPainter.curveTo(adapt(v1), adapt(v2), adapt(v3));
}
void DecorationPainter::doClosePath()
{
iPainter.closePath();
}
void DecorationPainter::doDrawPath(TPathMode mode)
{
iPainter.drawPath(mode);
}
// --------------------------------------------------------------------
void Group::draw(Painter &painter) const
{
if (!iDecoration.isNormal()) {
painter.pushMatrix();
auto m = painter.matrix();
painter.untransform(ETransformationsTranslations);
Rect r;
addToBBox(r, m, false);
double dx = 0.5 * (r.width() - 200.0);
double dy = 0.5 * (r.height() - 100.0);
DecorationPainter dp(painter, r.center(), dx, dy);
dp.translate(r.center() - Vector(200.0, 150.0));
dp.drawSymbol(iDecoration);
painter.popMatrix();
}
painter.pushMatrix();
painter.transform(matrix());
painter.untransform(transformations());
if (iClip.countSubPaths()) {
painter.push();
painter.newPath();
iClip.draw(painter);
painter.addClipPath();
}
for (const_iterator it = begin(); it != end(); ++it)
(*it)->draw(painter);
if (iClip.countSubPaths())
painter.pop();
painter.popMatrix();
}
void Group::drawSimple(Painter &painter) const
{
painter.pushMatrix();
painter.transform(matrix());
painter.untransform(transformations());
if (iClip.countSubPaths()) {
painter.push();
painter.newPath();
iClip.draw(painter);
painter.addClipPath();
}
for (const_iterator it = begin(); it != end(); ++it)
(*it)->drawSimple(painter);
if (iClip.countSubPaths())
painter.pop();
painter.popMatrix();
}
void Group::addToBBox(Rect &box, const Matrix &m, bool cp) const
{
Matrix m1 = m * matrix();
Rect tbox;
for (const_iterator it = begin(); it != end(); ++it) {
(*it)->addToBBox(tbox, m1, cp);
}
// now clip to clipping path
if (iClip.countSubPaths()) {
Rect cbox;
iClip.addToBBox(cbox, m1, false);
tbox.clipTo(cbox);
}
box.addRect(tbox);
}
//! Return total pinning status of group and its elements.
TPinned Group::pinned() const
{
return TPinned(Object::pinned() | iImp->iPinned);
}
double Group::distance(const Vector &v, const Matrix &m, double bound) const
{
double d = bound;
double d1;
Matrix m1 = m * matrix();
for (const_iterator it = begin(); it != end(); ++it) {
if ((d1 = (*it)->distance(v, m1, d)) < d)
d = d1;
}
return d;
}
void Group::snapVtx(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
Matrix m1 = m * matrix();
for (const_iterator it = begin(); it != end(); ++it)
(*it)->snapVtx(mouse, m1, pos, bound);
}
void Group::snapCtl(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
Matrix m1 = m * matrix();
for (const_iterator it = begin(); it != end(); ++it)
(*it)->snapCtl(mouse, m1, pos, bound);
}
void Group::snapBnd(const Vector &mouse, const Matrix &m,
Vector &pos, double &bound) const
{
Matrix m1 = m * matrix();
for (const_iterator it = begin(); it != end(); ++it)
(*it)->snapBnd(mouse, m1, pos, bound);
}
void Group::checkStyle(const Cascade *sheet,
AttributeSeq &seq) const
{
for (const_iterator it = begin(); it != end(); ++it)
(*it)->checkStyle(sheet, seq);
}
//! Set clip path for this group.
/*! Any previously set clip path is deleted. */
void Group::setClip(const Shape &clip)
{
iClip = clip;
}
//! Set link destination to use this group as a hyperlink.
void Group::setUrl(String url)
{
iUrl = url;
}
//! Create private implementation.
void Group::detach()
{
Imp *old = iImp;
iImp = new Imp;
iImp->iRefCount = 1;
iImp->iPinned = old->iPinned;
for (const_iterator it = old->iObjects.begin();
it != old->iObjects.end(); ++it)
iImp->iObjects.push_back((*it)->clone());
}
Attribute Group::getAttribute(Property prop) const noexcept
{
if (prop == EPropDecoration)
return iDecoration;
else
return Object::getAttribute(prop);
}
//! Set attribute on all children.
bool Group::setAttribute(Property prop, Attribute value)
{
if (prop == EPropPinned || prop == EPropTransformations)
return Object::setAttribute(prop, value);
if (prop == EPropDecoration) {
auto old = iDecoration;
iDecoration = value;
return (old != iDecoration);
}
// all others are handled by elements themselves
detach();
bool result = false;
for (List::iterator it = iImp->iObjects.begin();
it != iImp->iObjects.end(); ++it)
result |= (*it)->setAttribute(prop, value);
return result;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipelib/ipeutils.cpp 0000644 0001750 0001750 00000040050 13561570220 016630 0 ustar otfried otfried // --------------------------------------------------------------------
// Various utility classes
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipeutils.h"
#include "ipepage.h"
#include "ipegroup.h"
#include "ipereference.h"
#include "ipeimage.h"
#include "ipetext.h"
#include "ipelet.h"
#include
using namespace ipe;
// --------------------------------------------------------------------
/*! \class ipe::BitmapFinder
\ingroup high
\brief A visitor that recursively scans objects and collects all bitmaps.
*/
void BitmapFinder::scanPage(const Page *page)
{
for (int i = 0; i < page->count(); ++i)
page->object(i)->accept(*this);
}
void BitmapFinder::visitGroup(const Group *obj)
{
for (Group::const_iterator it = obj->begin(); it != obj->end(); ++it)
(*it)->accept(*this);
}
void BitmapFinder::visitImage(const Image *obj)
{
iBitmaps.push_back(obj->bitmap());
}
// --------------------------------------------------------------------
/*! \class ipe::BBoxPainter
\ingroup high
\brief Paint objects using this painter to compute an accurate bounding box.
The Object::bbox member function computes a bounding box useful
for distance calculations and optimizations. To find a bounding box
that is accurate for the actual \b drawn object, paint the object
using a BBoxPainter, and retrieve the box with bbox.
*/
BBoxPainter::BBoxPainter(const Cascade *style)
: Painter(style)
{
iClipBox.push_back(Rect()); // no clipping yet
}
void BBoxPainter::doPush()
{
iClipBox.push_back(iClipBox.back());
}
void BBoxPainter::doPop()
{
iClipBox.pop_back();
}
void BBoxPainter::doNewPath()
{
iPathBox.clear();
}
void BBoxPainter::doMoveTo(const Vector &v)
{
iV = v;
iPathBox.addPoint(iV);
}
void BBoxPainter::doLineTo(const Vector &v)
{
iV = v;
iPathBox.addPoint(iV);
}
void BBoxPainter::doCurveTo(const Vector &v1, const Vector &v2,
const Vector &v3)
{
Bezier bez(iV, v1, v2, v3);
Rect bb = bez.bbox();
iPathBox.addPoint(bb.bottomLeft());
iPathBox.addPoint(bb.topRight());
iV = v3;
}
void BBoxPainter::doDrawBitmap(Bitmap)
{
Rect box;
box.addPoint(matrix() * Vector(0.0, 0.0));
box.addPoint(matrix() * Vector(0.0, 1.0));
box.addPoint(matrix() * Vector(1.0, 1.0));
box.addPoint(matrix() * Vector(1.0, 0.0));
box.clipTo(iClipBox.back());
iBBox.addRect(box);
}
void BBoxPainter::doDrawText(const Text *text)
{
// this is not correct if the text is transformed
// as documented in the manual
Rect box;
box.addPoint(matrix() * Vector(0,0));
box.addPoint(matrix() * Vector(0, text->totalHeight()));
box.addPoint(matrix() * Vector(text->width(), text->totalHeight()));
box.addPoint(matrix() * Vector(text->width(), 0));
const TextPadding *pad = cascade()->findTextPadding();
box.addPoint(box.bottomLeft() - Vector(pad->iLeft, pad->iBottom));
box.addPoint(box.topRight() + Vector(pad->iRight, pad->iTop));
box.clipTo(iClipBox.back());
iBBox.addRect(box);
}
void BBoxPainter::doDrawPath(TPathMode mode)
{
double lw = pen().toDouble() / 2.0;
if (!iPathBox.isEmpty()) {
iPathBox.clipTo(iClipBox.back());
if (!iPathBox.isEmpty()) {
iBBox.addPoint(iPathBox.bottomLeft() - Vector(lw, lw));
iBBox.addPoint(iPathBox.topRight() + Vector(lw, lw));
}
}
}
void BBoxPainter::doAddClipPath()
{
if (iClipBox.back().isEmpty())
iClipBox.back() = iPathBox;
else
iClipBox.back().clipTo(iPathBox);
}
// --------------------------------------------------------------------
/*! \class ipe::A85Stream
\ingroup high
\brief Filter stream adding ASCII85 encoding.
*/
inline uint32_t a85word(const uint8_t *p)
{
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}
inline void a85encode(uint32_t w, char *p)
{
p[4] = char(w % 85 + 33);
w /= 85;
p[3] = char(w % 85 + 33);
w /= 85;
p[2] = char(w % 85 + 33);
w /= 85;
p[1] = char(w % 85 + 33);
p[0] = char(w / 85 + 33);
}
A85Stream::A85Stream(Stream &stream)
: iStream(stream)
{
iN = 0;
iCol = 0;
}
void A85Stream::putChar(char ch)
{
iCh[iN++] = ch;
if (iN == 4) {
// encode and write
uint32_t w = a85word(iCh);
if (w == 0) {
iStream.putChar('z');
++iCol;
} else {
char buf[6];
buf[5] = '\0';
a85encode(w, buf);
iStream.putCString(buf);
iCol += 5;
}
if (iCol > 70) {
iStream.putChar('\n');
iCol = 0;
}
iN = 0;
}
}
void A85Stream::close()
{
if (iN) {
for (int k = iN; k < 4; ++k)
iCh[k] = 0;
uint32_t w = a85word(iCh);
char buf[6];
a85encode(w, buf);
buf[iN + 1] = '\0';
iStream.putCString(buf);
}
iStream.putCString("~>\n");
iStream.close();
}
// --------------------------------------------------------------------
/*! \class ipe::Base64Stream
\ingroup high
\brief Filter stream adding Base64 encoding.
*/
static const char base64letter[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
inline uint32_t base64word(const uint8_t *p)
{
return (p[0] << 16) | (p[1] << 8) | p[2];
}
inline void base64encode(uint32_t w, char *p)
{
p[4] = '\0';
p[3] = base64letter[w % 64];
w >>= 6;
p[2] = base64letter[w % 64];
w >>= 6;
p[1] = base64letter[w % 64];
w >>= 6;
p[0] = base64letter[w % 64];
}
Base64Stream::Base64Stream(Stream &stream)
: iStream(stream)
{
iN = 0;
iCol = 0;
}
void Base64Stream::putChar(char ch)
{
iCh[iN++] = ch;
if (iN == 3) {
// encode and write
uint32_t w = base64word(iCh);
char buf[5];
base64encode(w, buf);
iStream.putCString(buf);
iCol += 4;
if (iCol > 70) {
iStream.putChar('\n');
iCol = 0;
}
iN = 0;
}
}
void Base64Stream::close()
{
if (iN) {
for (int k = iN; k < 3; ++k)
iCh[k] = 0;
uint32_t w = base64word(iCh);
char buf[5];
base64encode(w, buf);
for (int k = iN + 1; k < 4; ++k)
buf[k] = '=';
iStream.putCString(buf);
}
iStream.putCString("\n");
iStream.close();
}
// --------------------------------------------------------------------
/*! \class ipe::A85Source
\ingroup high
\brief Filter source adding ASCII85 decoding.
*/
A85Source::A85Source(DataSource &source)
: iSource(source)
{
iEof = false;
iN = 0; // number of characters buffered
iIndex = 0; // next character to return
}
int A85Source::getChar()
{
if (iIndex < iN)
return uint8_t(iBuf[iIndex++]);
if (iEof)
return EOF;
int ch;
do {
ch = iSource.getChar();
} while (ch == '\n' || ch == '\r' || ch == ' ');
if (ch == '~' || ch == EOF) {
iEof = true;
iN = 0; // no more data, immediate EOF
return EOF;
}
iIndex = 1;
iN = 4;
if (ch == 'z') {
iBuf[0] = iBuf[1] = iBuf[2] = iBuf[3] = 0;
return uint8_t(iBuf[0]);
}
int c[5];
c[0] = ch;
for (int k = 1; k < 5; ++k) {
do {
c[k] = iSource.getChar();
} while (c[k] == '\n' || c[k] == '\r' || c[k] == ' ');
if (c[k] == '~' || c[k] == EOF) {
iN = k - 1;
iEof = true;
break;
}
}
for (int k = iN + 1; k < 5; ++k)
c[k] = 0x21 + 84;
uint32_t t = 0;
for (int k = 0; k < 5; ++k)
t = t * 85 + (c[k] - 0x21);
for (int k = 3; k >= 0; --k) {
iBuf[k] = char(t & 0xff);
t >>= 8;
}
return iBuf[0];
};
// --------------------------------------------------------------------
/*! \class ipe::Base64Source
\ingroup high
\brief Filter source adding Base64 decoding.
*/
static signed char base64_value[] =
{ 62, -1, -1, -1, 63, // 2b..2f
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, // 30..3f
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40..4f
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 50..5f
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60..6f
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 70..7a
};
inline bool base64illegal(int ch)
{
return (ch < '+' || ch > 'z' || base64_value[ch - '+'] < 0);
}
inline int base64value(int ch)
{
return base64_value[ch - '+'];
}
Base64Source::Base64Source(DataSource &source)
: iSource(source)
{
iEof = false;
iIndex = 0; // buffer empty
iBufLen = 0;
}
int Base64Source::getChar()
{
if (iEof)
return EOF;
if (iIndex < iBufLen)
return uint8_t(iBuf[iIndex++]);
char buf[4];
for (int i = 0; i < 4; ++i) {
int ch;
do {
ch = iSource.getChar();
} while (ch == '\n' || ch == '\r' || ch == ' ');
// non-base64 characters terminate stream
if (ch == EOF || base64illegal(ch)) {
iEof = true; // no more data, immediate EOF
return EOF;
}
buf[i] = ch;
}
uint32_t w = base64value(buf[0]) << 18;
w |= (base64value(buf[1]) << 12);
w |= (base64value(buf[2]) << 6);
w |= base64value(buf[3]);
iBuf[0] = (w >> 16) & 0xff;
iBuf[1] = (w >> 8) & 0xff;
iBuf[2] = w & 0xff;
iBufLen = 3;
if (buf[3] == '=') {
--iBufLen;
if (buf[2] == '=')
--iBufLen;
}
iIndex = 1;
return uint8_t(iBuf[0]);
};
// --------------------------------------------------------------------
/*! \class ipe::DeflateStream
\ingroup high
\brief Filter stream adding flate compression.
*/
struct DeflateStream::Private {
z_stream iFlate;
};
DeflateStream::DeflateStream(Stream &stream, int level)
: iStream(stream), iIn(0x400), iOut(0x400) // create buffers
{
iPriv = new Private;
z_streamp z = &iPriv->iFlate;
z->zalloc = nullptr;
z->zfree = nullptr;
z->opaque = nullptr;
int err = ::deflateInit(z, level);
if (err != Z_OK) {
ipeDebug("deflateInit returns error %d", err);
assert(false);
}
iN = 0;
}
DeflateStream::~DeflateStream()
{
if (iPriv) {
z_streamp z = &iPriv->iFlate;
::deflateEnd(z);
delete iPriv;
}
}
void DeflateStream::putChar(char ch)
{
iIn[iN++] = ch;
if (iN < iIn.size())
return;
// compress and write
z_streamp z = &iPriv->iFlate;
z->next_in = (Bytef *) iIn.data();
z->avail_in = iIn.size();
while (z->avail_in) {
z->next_out = (Bytef *) iOut.data();
z->avail_out = iOut.size();
int err = ::deflate(z, Z_NO_FLUSH);
if (err != Z_OK) {
ipeDebug("deflate returns error %d", err);
assert(false);
}
// save output
iStream.putRaw(iOut.data(), z->next_out - (Bytef *) iOut.data());
}
iN = 0;
}
void DeflateStream::close()
{
// compress and write remaining data
z_streamp z = &iPriv->iFlate;
z->next_in = (Bytef *) iIn.data();
z->avail_in = iN;
int err;
do {
z->next_out = (Bytef *) iOut.data();
z->avail_out = iOut.size();
err = ::deflate(z, Z_FINISH);
if (err != Z_OK && err != Z_STREAM_END) {
ipeDebug("deflate returns error %d", err);
assert(false);
}
iStream.putRaw(iOut.data(), z->next_out - (Bytef *) iOut.data());
} while (err == Z_OK);
err = ::deflateEnd(z);
if (err != Z_OK) {
ipeDebug("deflateEnd returns error %d", err);
assert(false);
}
delete iPriv;
iPriv = nullptr; // make sure no more writing possible
iStream.close();
}
//! Deflate a buffer in a single run.
/*! The returned buffer may be larger than necessary: \a deflatedSize
is set to the number of bytes actually used. */
Buffer DeflateStream::deflate(const char *data, int size,
int &deflatedSize, int compressLevel)
{
uLong dfsize = uLong(size * 1.001 + 13);
Buffer deflatedData(dfsize);
int err = ::compress2((Bytef *) deflatedData.data(), &dfsize,
(const Bytef *) data, size, compressLevel);
if (err != Z_OK) {
ipeDebug("Zlib compress2 returns errror %d", err);
assert(false);
}
deflatedSize = dfsize;
return deflatedData;
}
// --------------------------------------------------------------------
/*! \class ipe::InflateSource
\ingroup high
\brief Filter source adding flate decompression.
*/
struct InflateSource::Private {
z_stream iFlate;
};
InflateSource::InflateSource(DataSource &source)
: iSource(source), iIn(0x400), iOut(0x400)
{
iPriv = new Private;
z_streamp z = &iPriv->iFlate;
z->zalloc = nullptr;
z->zfree = nullptr;
z->opaque = nullptr;
fillBuffer();
int err = ::inflateInit(z);
if (err != Z_OK) {
ipeDebug("inflateInit returns error %d", err);
delete iPriv;
iPriv = nullptr; // set EOF
return;
}
iP = iOut.data();
z->next_out = (Bytef *) iP;
}
InflateSource::~InflateSource()
{
if (iPriv) {
z_streamp z = &iPriv->iFlate;
::inflateEnd(z);
delete iPriv;
}
}
void InflateSource::fillBuffer()
{
char *p = iIn.data();
char *p1 = iIn.data() + iIn.size();
z_streamp z = &iPriv->iFlate;
z->next_in = (Bytef *) p;
z->avail_in = 0;
while (p < p1) {
int ch = iSource.getChar();
if (ch == EOF)
return;
*p++ = char(ch);
z->avail_in++;
}
}
//! Get one more character, or EOF.
int InflateSource::getChar()
{
if (!iPriv)
return EOF;
z_streamp z = &iPriv->iFlate;
if (iP < (char *) z->next_out)
return uint8_t(*iP++);
// next to decompress some data
if (z->avail_in == 0)
fillBuffer();
if (z->avail_in > 0) {
// data is available
z->next_out = (Bytef *) iOut.data();
z->avail_out = iOut.size();
int err = ::inflate(z, Z_NO_FLUSH);
if (err != Z_OK && err != Z_STREAM_END) {
ipeDebug("inflate returns error %d", err);
::inflateEnd(z);
delete iPriv;
iPriv = nullptr; // set EOF
return EOF;
}
iP = iOut.data();
if (iP < (char *) z->next_out)
return uint8_t(*iP++);
// didn't get any new data, must be EOF
}
// fillBuffer didn't get any data, must be EOF, so we are done
::inflateEnd(z);
delete iPriv;
iPriv = nullptr;
return EOF;
}
// --------------------------------------------------------------------
/*! \defgroup ipelet The Ipelet interface
\brief Implementation of Ipe plugins.
Ipelets are dynamically loaded plugins for Ipe written in Lua.
The Ipelet class makes it easy for ipelet authors to write ipelets
in C++ without using Lua's C API. They only need to provide some
boilerplate Lua code to define the labels and functions of the
ipelet, and use the Lua function "loadIpelet" to load a DLL
containing a C++ class derived from Ipelet. The run() method of
this class can then be called from Lua. The C++ code has access to
services provided by Ipe through an IpeletHelper object.
Ipelet derived classes are restricted to operate on the current page
of the document, and cannot modify the StyleSheet or other
properties of the document. If you wish to write an ipelet doing
this, you need to work in Lua (or create a C++ library using the Lua
C API).
*/
/*! \class ipe::Ipelet
\ingroup ipelet
\brief Abstract base class for Ipelets.
*/
//! Pure virtual destructor.
Ipelet::~Ipelet()
{
// nothing
}
// --------------------------------------------------------------------
/*! \class ipe::IpeletHelper
\ingroup ipelet
\brief Service provider for Ipelets.
C++ Ipelets can ask Ipe to perform various services and request
information using this class.
*/
//! Pure virtual destructor.
IpeletHelper::~IpeletHelper()
{
// nothing
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipeextract/ 0000755 0001750 0001750 00000000000 13561570220 015173 5 ustar otfried otfried ipe-7.2.13/src/ipeextract/ipeextract.cpp 0000644 0001750 0001750 00000025515 13561570220 020057 0 ustar otfried otfried // --------------------------------------------------------------------
// ipeextract
// --------------------------------------------------------------------
/*
This file is part of the extensible drawing editor Ipe.
Copyright (c) 1993-2019 Otfried Cheong
Ipe is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
As a special exception, you have permission to link Ipe with the
CGAL library and distribute executables, as long as you follow the
requirements of the Gnu General Public License in regard to all of
the software in the executable aside from CGAL.
Ipe is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with Ipe; if not, you can find it at
"http://www.gnu.org/copyleft/gpl.html", or write to the Free
Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ipexml.h"
#include "ipeutils.h"
#include "ipepdfparser.h"
#include
using namespace ipe;
// ---------------------------------------------------------------------
enum TFormat {EXml, EPdf, EEps, EIpe5, EUnknown};
String readLine(DataSource &source)
{
String s;
int ch = source.getChar();
while (ch != EOF && ch != '\n') {
s += char(ch);
ch = source.getChar();
}
return s;
}
//! Determine format of file in \a source.
TFormat fileFormat(DataSource &source)
{
String s1 = readLine(source);
String s2 = readLine(source);
if (s1.substr(0, 5) == " tag
if (lt && iCh == 'b') {
String tag;
while (isTagChar(iCh)) {
tag += char(iCh);
fputc(iCh, iOut);
getChar();
}
// at char after tag
if (tag == "bitmap" && !parseBitmap())
return false;
}
}
return true;
}
// write out attributes, but drop 'pdfObject'
void StreamParser::writeAttributes(const XmlAttributes &attr)
{
for (XmlAttributes::const_iterator it = attr.begin();
it != attr.end(); ++it)
if (it->first != "pdfObject")
fprintf(iOut, " %s=\"%s\"", it->first.z(), it->second.z());
fprintf(iOut, ">\n");
}
static void writeBits(FILE *out, Buffer bits)
{
const char *data = bits.data();
const char *fin = data + bits.size();
int col = 0;
while (data != fin) {
fprintf(out, "%02x", (*data++ & 0xff));
if (++col == 36) {
fputc('\n', out);
col = 0;
}
}
if (col > 0)
fputc('\n', out);
}
bool StreamParser::parseBitmap()
{
XmlAttributes attr;
if (!parseAttributes(attr))
return false;
String objNumStr;
if (attr.slash() && attr.has("pdfObject", objNumStr)) {
Lex lex(objNumStr);
Buffer bits = image(lex.getInt());
Buffer alpha;
lex.skipWhitespace();
if (!lex.eos()) {
alpha = image(lex.getInt());
fprintf(iOut, " alphaLength=\"%d\"", alpha.size());
}
fprintf(iOut, " length=\"%d\"", bits.size());
writeAttributes(attr);
writeBits(iOut, bits);
if (alpha.size() > 0)
writeBits(iOut, alpha);
fprintf(iOut, "\n");
} else {
// just write out attributes
writeAttributes(attr);
}
return true;
}
// --------------------------------------------------------------------
class StreamParserPdf : public StreamParser {
public:
explicit StreamParserPdf(PdfFile &loader, DataSource &source,
std::FILE *out)
: StreamParser(source, out), iLoader(loader) { /* nothing */ }
virtual Buffer image(int objNum);
private:
PdfFile &iLoader;
};
Buffer StreamParserPdf::image(int objNum)
{
const PdfObj *obj = iLoader.object(objNum);
if (!obj || !obj->dict() || obj->dict()->stream().size() == 0)
return Buffer();
return obj->dict()->stream();
}
// --------------------------------------------------------------------
class PsSource : public DataSource {
public:
PsSource(DataSource &source) : iSource(source) { /* nothing */ }
bool skipToXml();
String readLine();
Buffer image(int index) const;
int getNext() const;
inline bool deflated() const { return iDeflated; }
virtual int getChar();
private:
DataSource &iSource;
std::vector iImages;
bool iEos;
bool iDeflated;
};
int PsSource::getChar()
{
int ch = iSource.getChar();
if (ch == '\n')
iSource.getChar(); // remove '%'
return ch;
}
String PsSource::readLine()
{
String s;
int ch = iSource.getChar();
while (ch != EOF && ch != '\n') {
s += char(ch);
ch = iSource.getChar();
}
iEos = (ch == EOF);
return s;
}
Buffer PsSource::image(int index) const
{
if (1 <= index && index <= int(iImages.size()))
return iImages[index - 1];
else
return Buffer();
}
bool PsSource::skipToXml()
{
iDeflated = false;
String s1 = readLine();
String s2 = readLine();
if (s1.substr(0, 11) != "%!PS-Adobe-" ||
s2.substr(0, 11) != "%%Creator: ")
return false;
if (s2.substr(11, 6) == "Ipelib") {
// the 'modern' file format of Ipe 6.0 preview 17 and later
do {
s1 = readLine();
if (s1.substr(0, 17) == "%%BeginIpeImage: ") {
Lex lex(s1.substr(17));
int num, len;
lex >> num >> len;
if (num != int(iImages.size() + 1))
return false;
(void) readLine(); // skip 'image'
Buffer buf(len);
A85Source a85(iSource);
char *p = buf.data();
char *p1 = p + buf.size();
while (p < p1) {
int ch = a85.getChar();
if (ch == EOF)
return false;
*p++ = char(ch);
}
iImages.push_back(buf);
}
} while (!iEos && s1.substr(0, 13) != "%%BeginIpeXml");
iDeflated = (s1.substr(13, 14) == ": /FlateDecode");
} else {
// the 'old' file format generated through pdftops
do {
s1 = readLine();
} while (!iEos && s1.substr(0, 10) != "%%EndSetup");
}
if (iEos)
return false;
(void) iSource.getChar(); // skip '%' before
return true;
}
// --------------------------------------------------------------------
class StreamParserPs : public StreamParser {
public:
explicit StreamParserPs(PsSource &loader, DataSource &source,
std::FILE *out)
: StreamParser(source, out), iLoader(loader) { /* nothing */ }
virtual Buffer image(int objNum);
private:
PsSource &iLoader;
};
Buffer StreamParserPs::image(int objNum)
{
return iLoader.image(objNum);
}
// --------------------------------------------------------------------
static bool extractPs(DataSource &source, std::FILE *out)
{
PsSource psSource(source);
if (!psSource.skipToXml()) {
fprintf(stderr, "Could not find XML stream.\n");
return false;
}
if (psSource.deflated()) {
A85Source a85(psSource);
InflateSource source(a85);
StreamParserPs parser(psSource, source, out);
return parser.parse();
} else {
StreamParserPs parser(psSource, psSource, out);
return parser.parse();
}
return false;
}
static bool extractPdf(DataSource &source, std::FILE *out)
{
PdfFile loader;
if (!loader.parse(source)) {
fprintf(stderr, "Error parsing PDF file - probably not an Ipe file.\n");
return false;
}
// try ancient format version first (early previews of Ipe 6.0)
const PdfObj *obj = loader.catalog()->get("Ipe", &loader);
// otherwise try most recent format (>= 7.2.11)
if (!obj) {
obj = loader.catalog()->get("PieceInfo", &loader);
if (obj && obj->dict()) {
obj = obj->dict()->get("Ipe", &loader);
if (obj && obj->dict())
obj = obj->dict()->get("Private", &loader);
}
}
if (!obj)
obj = loader.object(1);
if (!obj || !obj->dict()) {
fprintf(stderr, "Input file does not contain an Ipe XML stream.\n");
return false;
}
const PdfObj *type = obj->dict()->get("Type", nullptr);
if (!type || !type->name() || type->name()->value() != "Ipe") {
fprintf(stderr, "Input file does not contain an Ipe XML stream.\n");
return false;
}
Buffer buffer = obj->dict()->stream();
BufferSource xml(buffer);
if (obj->dict()->deflated()) {
InflateSource xml1(xml);
StreamParserPdf parser(loader, xml1, out);
return parser.parse();
} else {
StreamParserPdf parser(loader, xml, out);
return parser.parse();
}
}
// --------------------------------------------------------------------
static void usage()
{
fprintf(stderr,
"Usage: ipeextract ( | ) []\n"
"Ipeextract extracts the XML stream from a PDF or Postscript file\n"
"generated by any version of Ipe 6 or Ipe 7.\n"
);
exit(1);
}
int main(int argc, char *argv[])
{
Platform::initLib(IPELIB_VERSION);
// ensure one or two arguments
if (argc != 2 && argc != 3)
usage();
const char *src = argv[1];
String dst;
if (argc == 3) {
dst = argv[2];
} else {
String s = src;
if (s.right(4) == ".pdf" || s.right(4) == ".eps")
dst = s.left(s.size() - 3) + "xml";
else
dst = s + ".xml";
}
std::FILE *fd = Platform::fopen(src, "rb");
if (!fd) {
std::fprintf(stderr, "Could not open '%s'\n", src);
exit(1);
}
FileSource source(fd);
TFormat format = fileFormat(source);
if (format == EXml) {
fprintf(stderr, "Input file is already in XML format.\n");
} else if (format == EIpe5) {
fprintf(stderr, "Input file is in Ipe5 format.\n"
"Run 'ipe5toxml' to convert it to XML format.\n");
} else {
std::rewind(fd);
std::FILE *out = Platform::fopen(dst.z(), "wb");
if (!out) {
fprintf(stderr, "Could not open '%s' for writing.\n", dst.z());
} else {
bool res = (format == EPdf) ?
extractPdf(source, out) : extractPs(source, out);
if (!res)
fprintf(stderr, "Error during extraction of XML stream.\n");
std::fclose(out);
}
}
std::fclose(fd);
return 0;
}
// --------------------------------------------------------------------
ipe-7.2.13/src/ipeextract/Makefile 0000644 0001750 0001750 00000001342 13561570220 016633 0 ustar otfried otfried # --------------------------------------------------------------------
# Makefile for Ipeextract
# --------------------------------------------------------------------
OBJDIR = $(BUILDDIR)/obj/ipeextract
include ../common.mak
TARGET = $(call exe_target,ipeextract)
CPPFLAGS += -I../include
LIBS += -L$(buildlib) -lipe
all: $(TARGET)
sources = ipeextract.cpp
$(TARGET): $(objects)
$(MAKE_BINDIR)
$(CXX) $(LDFLAGS) -o $@ $^ $(LIBS)
clean:
@-rm -f $(objects) $(TARGET) $(DEPEND)
$(DEPEND): Makefile
$(MAKE_DEPEND)
-include $(DEPEND)
install: $(TARGET)
$(INSTALL_DIR) $(INSTALL_ROOT)$(IPEBINDIR)
$(INSTALL_PROGRAMS) $(TARGET) $(INSTALL_ROOT)$(IPEBINDIR)
# --------------------------------------------------------------------
ipe-7.2.13/src/common.mak 0000644 0001750 0001750 00000017303 13561570220 015011 0 ustar otfried otfried # -*- makefile -*-
# --------------------------------------------------------------------
#
# Building Ipe --- common definitions
#
# --------------------------------------------------------------------
# Are we compiling for Windows? For Mac OS X?
ifdef COMSPEC
WIN32 = 1
IPEBUNDLE = 1
IPEUI = WIN32
else ifdef IPECROSS
WIN32 = 1
IPEBUNDLE = 1
IPEUI = WIN32
else
UNAME = $(shell uname)
ifeq "$(UNAME)" "Darwin"
MACOS = 1
IPEUI = COCOA
IPECONFIGMAK ?= macos.mak
else
IPEUI ?= QT
ifdef IPESNAPCRAFT
IPECONFIGMAK ?= snapcraft.mak
else ifdef IPEAPPIMAGE
IPECONFIGMAK ?= snapcraft.mak
else
IPECONFIGMAK ?= config.mak
endif
endif
endif
# --------------------------------------------------------------------
# IPESRCDIR is Ipe's top "src" directory
# if 'common.mak' is included on a different level than a subdirectory
# of "src", then IPESRCDIR must be set before including 'common.mak'.
IPESRCDIR ?= ..
# --------------------------------------------------------------------
# Read configuration options (not used on Win32)
ifndef WIN32
include $(IPESRCDIR)/$(IPECONFIGMAK)
BUILDDIR = $(IPESRCDIR)/../build
endif
# --------------------------------------------------------------------
# set variables that depend on UI library
# -------------------- QT --------------------
ifeq ($(IPEUI),QT)
CPPFLAGS += -DIPEUI_QT
# Qt5 requires -fPIC depending on its own compilation settings.
CPPFLAGS += -fPIC
CXXFLAGS += -fPIC
DLL_CFLAGS = -fPIC
IPEUI_QT := 1
UI_CFLAGS = $(QT_CFLAGS)
UI_LIBS = $(QT_LIBS)
moc_sources = $(addprefix moc_, $(subst .h,.cpp,$(moc_headers)))
all_sources = $(sources) $(qt_sources)
objects = $(addprefix $(OBJDIR)/, $(subst .cpp,.o,$(all_sources) \
$(moc_sources)))
# -------------------- WIN32 --------------------
else ifeq ($(IPEUI), WIN32)
CPPFLAGS += -DIPEUI_WIN32
IPEUI_WIN32 := 1
UI_CFLAGS :=
UI_LIBS := -lcomctl32 -lcomdlg32 -lgdi32 -lgdiplus
all_sources = $(sources) $(win_sources)
objects = $(addprefix $(OBJDIR)/, $(subst .cpp,.o,$(all_sources)))
# -------------------- COCOA --------------------
else ifeq ($(IPEUI), COCOA)
CPPFLAGS += -DIPEUI_COCOA
IPEUI_COCOA := 1
CXXFLAGS += -mmacosx-version-min=10.10 -Wdeprecated-declarations
LDFLAGS += -mmacosx-version-min=10.10
UI_CFLAGS = $(IPEOBJCPP)
UI_LIBS = -framework Cocoa -framework AppKit \
-framework ApplicationServices
all_sources = $(sources) $(cocoa_sources)
objects = $(addprefix $(OBJDIR)/, $(subst .cpp,.o,$(all_sources)))
# -------------------- GTK --------------------
else ifeq ($(IPEUI), GTK)
CPPFLAGS += -DIPEUI_GTK -DGDK_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED
IPEUI_GTK := 1
GTK_CFLAGS ?= $(shell pkg-config --cflags gtk+-2.0)
GTK_LIBS ?= $(shell pkg-config --libs gtk+-2.0)
UI_CFLAGS = $(GTK_CFLAGS)
UI_LIBS = $(GTK_LIBS)
all_sources = $(sources) $(gtk_sources)
BUILDDIR = $(IPESRCDIR)/../gtkbuild
objects = $(addprefix $(OBJDIR)/, $(subst .cpp,.o,$(all_sources)))
else
error("Unknown IPEUI selected")
endif
CXXFLAGS += -Wall
ifdef IPESTRICT
CXXFLAGS += -Wsign-compare -Wzero-as-null-pointer-constant -Werror -DIPESTRICT
CPPFLAGS += -std=c++17
else
CPPFLAGS += -std=c++14
endif
ifdef IPECXX
CXX = $(IPECXX)
endif
DEPEND ?= $(OBJDIR)/depend.mak
.PHONY: clean
.PHONY: install
.PHONY: all
# Variables depending on platform
ifdef WIN32
# -------------------- WIN32 --------------------
# Set just in case user has environment variables set
IPEDOCDIR :=
IPEICONDIR :=
IPECURL := 1
IPE_NO_IPELIB_VERSION_CHECK := 1
CPPFLAGS += -DWIN32 -DUNICODE -D_UNICODE
DLL_LDFLAGS += -shared
PLUGIN_LDFLAGS += -shared
DL_LIBS :=
JPEG_CFLAGS :=
JPEG_LIBS := -lgdiplus
PNG_CFLAGS :=
PNG_LIBS := -lgdiplus
buildlib = $(BUILDDIR)/bin
buildbin = $(BUILDDIR)/bin
buildipelets = $(BUILDDIR)/ipelets
exe_target = $(BUILDDIR)/bin/$1.exe
dll_target = $(buildlib)/$1.dll
soname =
dll_symlinks =
install_symlinks =
ipelet_target = $(BUILDDIR)/ipelets/$1.dll
ifeq ($(IPECROSS),i686)
BUILDDIR = $(IPESRCDIR)/../mingw32
else
BUILDDIR = $(IPESRCDIR)/../mingw64
endif
CXXFLAGS += -g -O2
ifdef IPECROSS
# --------------- Cross compiling with Mingw-w64 ---------------
# http://mingw-w64.sourceforge.net/
CXX = $(IPECROSS)-w64-mingw32-g++
CC = $(IPECROSS)-w64-mingw32-gcc
STRIP_TARGET = $(IPECROSS)-w64-mingw32-strip $(TARGET)
WINDRES = $(IPECROSS)-w64-mingw32-windres
IPEDEPS ?= /sw/mingwlibs-$(IPECROSS)
else
# --------------- Compiling with Mingw-w64 under Windows ---------------
WINDRES = windres.exe
CXXFLAGS += -g -O2
STRIP_TARGET = strip $(TARGET)
IPEDEPS ?= /mingwlibs
endif
ZLIB_CFLAGS := -I$(IPEDEPS)/include
ZLIB_LIBS := -L$(IPEDEPS)/lib -lz
FREETYPE_CFLAGS := -I$(IPEDEPS)/include/freetype2 \
-I$(IPEDEPS)/include
FREETYPE_LIBS := -L$(IPEDEPS)/lib -lfreetype
CAIRO_CFLAGS := -I$(IPEDEPS)/include/cairo
CAIRO_LIBS := -L$(IPEDEPS)/lib -lcairo
LUA_CFLAGS := -I$(IPEDEPS)/lua53/include
LUA_LIBS := $(IPEDEPS)/lua53/lua53.dll
CURL_CFLAGS := -DCURL_STATICLIB -I$(IPEDEPS)/include
CURL_LIBS := -L$(IPEDEPS)/lib -lcurl -lcrypt32 -lz -lws2_32
else
ifdef MACOS
# -------------------- Mac OS X --------------------
CXXFLAGS += -g -Os
IPEOBJCPP = -x objective-c++ -fobjc-arc
DLL_LDFLAGS += -dynamiclib
PLUGIN_LDFLAGS += -bundle
DL_LIBS ?= -ldl
ZLIB_CFLAGS ?=
ZLIB_LIBS ?= -lz
JPEG_CFLAGS ?=
JPEG_LIBS ?= -framework ApplicationServices
ifdef IPEBUNDLE
IPELIBDIRINFO = @executable_path/../Frameworks
BUNDLEDIR ?= $(BUILDDIR)/Ipe.app/Contents
RESOURCEDIR = $(BUNDLEDIR)/Resources
buildlib = $(BUNDLEDIR)/Frameworks
buildbin = $(BUNDLEDIR)/MacOS
buildipelets = $(RESOURCEDIR)/ipelets
exe_target = $(BUNDLEDIR)/MacOS/$1
ipelet_target = $(RESOURCEDIR)/ipelets/$1.so
IPEAPP = 1
else
IPELIBDIRINFO ?= $(IPELIBDIR)
buildlib = $(BUILDDIR)/lib
exe_target = $(BUILDDIR)/bin/$1
ipelet_target = $(BUILDDIR)/ipelets/$1.so
buildbin = $(BUILDDIR)/bin
buildipelets = $(BUILDDIR)/ipelets
endif
soname = -Wl,-dylib_install_name,$(IPELIBDIRINFO)/lib$1.$(IPEVERS).dylib
dll_target = $(buildlib)/lib$1.$(IPEVERS).dylib
dll_symlinks = ln -sf lib$1.$(IPEVERS).dylib $(buildlib)/lib$1.dylib
install_symlinks = ln -sf lib$1.$(IPEVERS).dylib \
$(INSTALL_ROOT)$(IPELIBDIR)/lib$1.dylib
else
# -------------------- Unix --------------------
CXXFLAGS += -g -O2
DLL_LDFLAGS += -shared
PLUGIN_LDFLAGS += -shared
soname = -Wl,-soname,lib$1.so.$(IPEVERS)
dll_target = $(buildlib)/lib$1.so.$(IPEVERS)
dll_symlinks = ln -sf lib$1.so.$(IPEVERS) $(buildlib)/lib$1.so
install_symlinks = ln -sf lib$1.so.$(IPEVERS) \
$(INSTALL_ROOT)$(IPELIBDIR)/lib$1.so
buildlib = $(BUILDDIR)/lib
buildbin = $(BUILDDIR)/bin
buildipelets = $(BUILDDIR)/ipelets
exe_target = $(BUILDDIR)/bin/$1
ipelet_target = $(BUILDDIR)/ipelets/$1.so
endif
endif
# Macros
INSTALL_DIR = install -d
INSTALL_FILES = install -m 0644
INSTALL_SCRIPTS = install -m 0755
INSTALL_PROGRAMS = install -m 0755
MAKE_BINDIR = mkdir -p $(buildbin)
MAKE_LIBDIR = mkdir -p $(buildlib)
MAKE_IPELETDIR = mkdir -p $(buildipelets)
MAKE_DEPEND = \
mkdir -p $(OBJDIR); \
echo "" > $@; \
for f in $(all_sources); do \
$(CXX) -MM -MT $(OBJDIR)/$${f%%.cpp}.o $(CPPFLAGS) $$f >> $@; done
# The rules
$(OBJDIR)/%.o: %.cpp
@echo Compiling $(