diff --git a/MessageBundle.properties b/MessageBundle.properties new file mode 100644 index 0000000..d98b861 --- /dev/null +++ b/MessageBundle.properties @@ -0,0 +1,75 @@ +Project = Project +ProjectDetails = Project Details +Speakers = Speakers +TimeCoding = Time Coding +AdjustTimeCode = Adjust Time Code +In = Start +Out = Stop +PlaySegment = Play Segment +NewProject = New Project +TitleColon = Title: +TypeNewTitleColon = Type new title: +NoMedia = No media +MediaColon = Media: +NoTranscript = No transcript +TranscriptColon = Transcript: +KeyboardInputMethod = Keyboard Input Method +TranscriberName = Transcriber Name +TranscriberTask = Transcriber Task +Add = Add +FaceColon = Face: +NameColon = Name: +EnterNewSpeakerInfo = Enter new speaker info +Edit = Edit +EditSpeakerInfo = Edit speaker info +Remove = Remove +SureDeleteSpeaker = Are you sure you want to delete this speaker? +Warning = Warning! +Face = Face +Name = Name +SaveCurrent = Save the current transcript +OpenExisting = Open an existing transcript +WhatToDo = What do you want to do? +ChooseOne = Choose an option +Play = Play +Pause = Pause +Mode = Mode +Edit = Edit +ReadOnly = Read-only +View = View +Previous = Previous +Current = Current +Next = Next +SuppressTimes = Hide times +Search = Search +FindText = Find text +ReplaceText = Replace text +FindSpeaker = Find speaker +FindTime = Find time +Cut = Cut +Copy = Copy +Paste = Paste +SelectAll = Select all +InsertSpeaker = Insert speaker +InsertOneTime = Insert a time +InsertTwoTimes = Insert two times +New = New +Open = Open +Close = Close +Save = Save +SaveAs = Save as +Quit = Quit +Keyboard = Keyboard +Preferences = Preferences +FindHeading = Search +FindReplaceHeading = Search and Replace +FindWhat = Find what +ReplaceWith = Replace with +ReplaceQ = Replace? +FindNextYesNo = Find next? +StartFromBeginning = You've reached the end of the document. Do you want to continue searching from the beginning? +TextFound = Text found. +Replace = Replace +ReplaceAll = Replace all +NoReplace = Don't replace +Cancel = Cancel \ No newline at end of file diff --git a/MessageBundle_de_DE.properties b/MessageBundle_de_DE.properties new file mode 100644 index 0000000..125956b --- /dev/null +++ b/MessageBundle_de_DE.properties @@ -0,0 +1,75 @@ +Project = Project Details +ProjectDetails = Projekt +Speakers = Sprecher +TimeCoding = Zeitcode +AdjustTimeCode = Zeitcode regulieren +In = Start +Out = Stop +PlaySegment = Abschnitt abspielen +NewProject = Neues Projekt +TitleColon = Titel: +TypeNewTitleColon = Neue Titeleingabe +NoMedia = Keine Audio/Video-Medien +MediaColon = Audio/Video-Medien: +NoTranscript = Keine Transkription +TranscriptColon = Text-Transkription: +KeyboardInputMethod = Tastatur Eingabemethode +TranscriberName = Transkribierer-Name: +TranscriberTask = Transkribierer-Aufgabe: +Add = Hinzuf黦en +FaceColon = Gesicht +NameColon = Name: +EnterNewSpeakerInfo = Eingabe neue Sprecher-Information +Edit = Bearbeiten +EditSpeakerInfo = Bearbeiten der Srecher-Information +Remove = Entfernen +SureDeleteSpeaker = Wollen Sie wirklich diesen Sprecher l鰏chen? +Warning = Warnung! +Face = Gesicht +Name = Name +SaveCurrent = Gegenw鋜tige Transkription speichern +OpenExisting = Bereits bearbeitete Transkription 鰂fnen +WhatToDo = Was m鯿hten Sie tun? +ChooseOne = W鋒len Sie eine M鰃lichkeit +Play = Abspielen +Pause = Pause +Mode = Moduswahl +Edit = Bearbeiten der Transkription +ReadOnly = Sichten der Transkription +View = Sichten +Previous = Vorhergehender Satz +Current = Gegenw鋜tiger Satz +Next = N鋍hster Satz +SuppressTimes = Entfernen der Zeitzeichen +Search = Search +FindText = Text finden +ReplaceText = Text ersetzen +FindSpeaker = Sprecher finden +FindTime = Zeitabschnitt finden +Cut = Schneiden +Copy = Kopieren +Paste = Einf黦en +SelectAll = Alles ausw鋒len +InsertSpeaker = Sprecher einf黦en +InsertOneTime = Einmal einf黦en +InsertTwoTimes = Zweimal einf黦en +New = Neu +Open = 謋fnen +Close = Schlie遝n +Save = Speichern +SaveAs = Speichern als +Quit = Quit +Keyboard = Tastatur Eingabemethode +Preferences = Pr鋐erenzen +FindHeading = Search +FindReplaceHeading = Search and Replace +FindWhat = Find what +ReplaceWith = Replace with +ReplaceQ = Replace? +FindNextYesNo = Find next? +StartFromBeginning = You've reached the end of the document. Do you want to continue searching from the beginning? +TextFound = Text found. +Replace = Replace +ReplaceAll = Replace all +NoReplace = Don't replace +Cancel = Cancel \ No newline at end of file diff --git a/MessageBundle_zh_CN.properties b/MessageBundle_zh_CN.properties new file mode 100644 index 0000000..6e7084c --- /dev/null +++ b/MessageBundle_zh_CN.properties @@ -0,0 +1,34 @@ +Project = \u9879\u76ee +Speakers = \u5bf9\u8bdd\u8005 +TimeCoding = \u65f6\u95f4\u4ee3\u7801 +AdjustTimeCode = \u8c03\u6574\u65f6\u95f4 +In =\u5f00\u59cb +Out = \u7ed3\u675f +PlaySegment = \u663e\u793a\u7a97\u53e3 +NewProject =\u65e0\u65b0\u9879\u76ee\u6587\u4ef6 +TitleColon = \u9898\u76ee +TypeNewTitleColon = \u65b0\u9898\u76ee +NoMedia = \u65e0\u5a92\u4f53\u6587\u4ef6 +MediaColon = \u5a92\u4f53 +NoTranscript = \u65e0\u6587\u672c\u6587\u4ef6 +TranscriptColon = \u6587\u5b57 +KeyboardInputMethod = \u952e\u76d8\u5e03\u5c40 +TranscriberName = \u8bb0\u5f55\u8005 +TranscriberTask = \u8bb0\u5f55\u5185\u5bb9 +Add = \u589e\u52a0 +FaceColon = \u9009\u62e9\u8138\u5f62 +NameColon = \u53d6\u540d +EnterNewSpeakerInfo = \u589e\u52a0\u65b0\u7528\u6237\u7a97\u53e3 +Edit = \u7f16\u8f91 +EditSpeakerInfo = \u7f16\u8f91\u7a97\u53e3 +Remove = \u5220\u9664 +SureDeleteSpeaker = \u786e\u5b9e\u5220\u9664\u5417\uff1f +Warning = \u8b66\u544a\uff01 +Face = \u8138 +Name = \u540d +SaveCurrent = \u4fdd\u5b58 +OpenExisting =\u6253\u5f00 +WhatToDo =\u9009\u62e9 +ChooseOne =\u9009\u62e9\u4e4b\u4e00 +Play = \u64ad\u653e +Pause = \u6682\u505c diff --git a/chinesebundle.txt b/chinesebundle.txt new file mode 100644 index 0000000..b4c2d82 --- /dev/null +++ b/chinesebundle.txt @@ -0,0 +1,34 @@ +Project = 项目 +Speakers = 对话者 +TimeCoding = 时间代码 +AdjustTimeCode = 调整时间 +In =开始 +Out = 结束 +PlaySegment = 显示窗口 +NewProject =无新项目文件 +TitleColon = 题目 +TypeNewTitleColon = 新题目 +NoMedia = 无媒体文件 +MediaColon = 媒体 +NoTranscript = 无文本文件 +TranscriptColon = 文字 +KeyboardInputMethod = 键盘布局 +TranscriberName = 记录者 +TranscriberTask = 记录内容 +Add = 增加 +FaceColon = 选择脸形 +NameColon = 取名 +EnterNewSpeakerInfo = 增加新用户窗口 +Edit = 编辑 +EditSpeakerInfo = 编辑窗口 +Remove = 删除 +SureDeleteSpeaker = 确实删除吗? +Warning = 警告! +Face = 脸 +Name = 名 +SaveCurrent = 保存 +OpenExisting =打开 +WhatToDo =选择 +ChooseOne =选择之一 +Play = 播放 +Pause = 暂停 \ No newline at end of file diff --git a/org/thdl/quilldriver/QD.java b/org/thdl/quilldriver/QD.java new file mode 100644 index 0000000..508b964 --- /dev/null +++ b/org/thdl/quilldriver/QD.java @@ -0,0 +1,1651 @@ +package org.thdl.quilldriver; + +import java.util.*; +import java.io.*; +import java.net.*; +import javax.swing.*; +import javax.swing.table.*; +import javax.swing.event.*; +import javax.swing.text.*; +import javax.swing.plaf.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.dnd.*; +import java.awt.datatransfer.*; +import org.thdl.tib.input.*; +import org.thdl.tib.text.*; +import org.thdl.tib.text.TibetanDocument.*; + +import org.jdom.*; +import org.jdom.input.SAXBuilder; +import org.jdom.output.XMLOutputter; +import org.jdom.transform.JDOMSource; +import javax.xml.transform.stream.*; +import javax.xml.transform.*; + + +public class QD extends JDesktopPane { + //project related data + protected Project project; + + //speaker related + protected SpeakerTable speakerTable; + + //video related + protected QDPlayer player = null; + + //frame related + protected JInternalFrame videoFrame = null; + protected JInternalFrame textFrame = null; + protected JInternalFrame actionFrame = null; + + //miscellaneous stuff + protected JFileChooser fileChooser = null; + public JTabbedPane jtp; + public TimeCodeManager tcp = null; + public SpeakerManager spm = null; + public JTextPane pane; + public Hashtable actions; + public ImageIcon clockIcon; + public static final String componentStyleName = "Component"; + public Style componentStyle; + public DataFlavor timeFlavor; + + protected JMenu editMenu; + + protected ResourceBundle messages; + + protected java.util.List work; + protected Work currentWork; + + protected DuffPane sharedDP = new DuffPane(); + protected DuffPane sharedDP2 = new DuffPane(); + + protected TibetanDocument findDoc = null; + protected TibetanDocument replaceDoc = null; + +protected URL keyboard_url = null; + + +public QD(ResourceBundle messages) { + this.messages = messages; + + setBackground(new JFrame().getBackground()); + setDragMode(JDesktopPane.OUTLINE_DRAG_MODE); + + clockIcon = new ImageIcon(QD.class.getResource("clock.gif")); + timeFlavor = new DataFlavor(Integer.class, "time"); + + fileChooser = new JFileChooser(); + fileChooser.addChoosableFileFilter(new XMLFilter()); + + Action insert1TimeAction = new AbstractAction("insert1Time", null) { + public void actionPerformed(ActionEvent e) { + new TimePoint(pane, clockIcon, tcp.getOutTime()); + tcp.setInTime(tcp.getOutTime().intValue()); + tcp.setOutTime(player.getLastTime()); + } + }; + + Action insert2TimesAction = new AbstractAction("insert2Times", null) { + public void actionPerformed(ActionEvent e) { + int p1 = pane.getSelectionStart(); + int p2 = pane.getSelectionEnd(); + pane.setCaretPosition(p1); + new TimePoint(pane, clockIcon, tcp.getInTime()); + pane.setCaretPosition(p2+1); + new TimePoint(pane, clockIcon, tcp.getOutTime()); + if (p1 == p2) + pane.setCaretPosition(pane.getCaretPosition()-1); + tcp.setInTime(tcp.getOutTime().intValue()); + tcp.setOutTime(player.getLastTime()); + } + }; + + Action saveAction = new AbstractAction("saveTranscript", null) { + public void actionPerformed(ActionEvent e) { + getSave(); + } + }; + + JTextPane tp = new JTextPane(); + Keymap keymap = tp.addKeymap("QDBindings", tp.getKeymap()); + + KeyStroke insert1TimeKey = KeyStroke.getKeyStroke(KeyEvent.VK_OPEN_BRACKET, Event.CTRL_MASK); + KeyStroke insert2TimesKey = KeyStroke.getKeyStroke(KeyEvent.VK_CLOSE_BRACKET, Event.CTRL_MASK); + KeyStroke saveKey = KeyStroke.getKeyStroke(KeyEvent.VK_S, Event.CTRL_MASK); + + keymap.addActionForKeyStroke(insert1TimeKey, insert1TimeAction); + keymap.addActionForKeyStroke(insert2TimesKey, insert2TimesAction); + keymap.addActionForKeyStroke(saveKey, saveAction); + + Speaker[] speakers = new Speaker[0]; + SpeakerData speakerData = new SpeakerData(speakers, keymap); + speakerTable = new SpeakerTable(speakerData); + + pane = new DuffPane(); + pane.setKeymap(keymap); + new TimePointDropTarget(pane); + + work = new ArrayList(); + currentWork = new Work(); + work.add(currentWork); + + JPanel textPanel = new JPanel(new BorderLayout()); + textPanel.add("Center", new JScrollPane(pane)); + + JPanel actionPanel = new JPanel(new BorderLayout()); + jtp = new JTabbedPane(); + project = null; + spm = new SpeakerManager(speakerTable); + jtp.addTab(messages.getString("Speakers"), spm); + actionPanel.add("Center", jtp); + + //(String title, boolean resizable, boolean closable, boolean maximizable, boolean iconifiable) + videoFrame = new JInternalFrame(null, false, false, false, false); + videoFrame.setVisible(true); + videoFrame.setLocation(0,0); + videoFrame.setSize(0,0); + add(videoFrame, JLayeredPane.POPUP_LAYER); + invalidate(); + validate(); + repaint(); + + textFrame = new JInternalFrame(null, false, false, false, true); + textFrame.setVisible(true); + textFrame.setContentPane(textPanel); + textFrame.setLocation(0,0); + textFrame.setSize(0,0); + textFrame.setJMenuBar(getTextMenuBar()); + add(textFrame, JLayeredPane.DEFAULT_LAYER); + invalidate(); + validate(); + repaint(); + + actionFrame = new JInternalFrame(null, false, false, false, true); + actionFrame.setVisible(true); + actionFrame.setContentPane(actionPanel); + actionFrame.setLocation(0,0); + actionFrame.setSize(0,0); + add(actionFrame, JLayeredPane.DEFAULT_LAYER); + invalidate(); + validate(); + repaint(); + + addComponentListener(new ComponentAdapter() { + public void componentResized(ComponentEvent ce) { + Dimension d = videoFrame.getSize(); + if (d.width == 0 && d.height == 0) + videoFrame.setSize(getSize().width / 2, 0); + textFrame.setLocation(videoFrame.getSize().width, 0); + textFrame.setSize(getSize().width - videoFrame.getSize().width, getSize().height); + actionFrame.setLocation(0, videoFrame.getSize().height); + actionFrame.setSize(videoFrame.getSize().width, getSize().height - videoFrame.getSize().height); + } + }); +} + +private void startTimer() { + final java.util.Timer timer = new java.util.Timer(true); + timer.schedule(new TimerTask() { + public void run() + { + if (player.cmd_isSized()) + { + timer.cancel(); + + if (tcp != null) + jtp.remove(tcp); + tcp = new TimeCodeManager(); + jtp.addTab(messages.getString("TimeCoding"), tcp); + + videoFrame.setContentPane(player); + videoFrame.pack(); + videoFrame.setMaximumSize(videoFrame.getSize()); + invalidate(); + validate(); + repaint(); + textFrame.setLocation(videoFrame.getSize().width, 0); + textFrame.setSize(getSize().width - videoFrame.getSize().width, getSize().height); + invalidate(); + validate(); + repaint(); + actionFrame.setLocation(0, videoFrame.getSize().height); + actionFrame.setSize(videoFrame.getSize().width, getSize().height - videoFrame.getSize().height); + invalidate(); + validate(); + repaint(); + } + }}, 0, 50); +} + +private void createActionTable(JTextComponent textComponent) { + actions = new Hashtable(); + Action[] actionsArray = textComponent.getActions(); + for (int i = 0; i < actionsArray.length; i++) { + Action a = actionsArray[i]; + actions.put(a.getValue(Action.NAME), a); + } +} + +private Action getActionByName(String name) { + return (Action)(actions.get(name)); +} + +class TimePoint extends JLabel implements DragGestureListener, DragSourceListener { + StyledDocument doc; + Position pos; + Integer time; + + TimePoint(final JTextPane tp, Icon icon, Integer time) { + super(icon); + this.time = time; + setCursor(new Cursor(Cursor.HAND_CURSOR)); + setToolTipText(getTimeAsString()); + DragSource dragSource = DragSource.getDefaultDragSource(); + dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, this); + addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) + playSegment(); + else { + SpinnerNumberModel snm1 = new SpinnerNumberModel(0, 0, player.getLastTime(), 10); + JSpinner spinner = new JSpinner(snm1); + spinner.setPreferredSize(new Dimension(100, 40)); + spinner.setValue(getTime()); + if (JOptionPane.showOptionDialog(tp, spinner, messages.getString("AdjustTimeCode"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, null, null) == JOptionPane.OK_OPTION) + setTime((Integer)spinner.getValue()); + } + } + }); + doc = tp.getStyledDocument(); + tp.insertComponent(this); + try { //insertions occur prior to position, so position should be right before icon after icon is inserted + pos = doc.createPosition(tp.getCaretPosition()-1); + } catch (BadLocationException ble) { + ble.printStackTrace(); + } + } + public void setTime(Integer t) { + time = t; + setToolTipText(getTimeAsString()); + } + public Integer getTime() { + return time; + } + public String getTimeAsString() { + return String.valueOf(time); + } + public void playSegment() { + int i=pos.getOffset(); + System.out.println(String.valueOf(i)); + for (i++; i in.intValue()) + player.cmd_play(in, out); + } + }); + JPanel ps = new JPanel(); + ps.add(playSegButton); + +/* + JButton playButton = new JButton(messages.getString("Play")); + JButton pauseButton = new JButton(messages.getString("Pause")); + + playButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (player != null) + player.cmd_play(); + } + }); + pauseButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (player != null) + player.cmd_stop(); + } + }); +*/ + + Box box = Box.createVerticalBox(); + box.add(inPanel); + box.add(outPanel); + box.add(ps); + + add("North", box); + } + public Integer getInTime() { + return (Integer)inSpinner.getValue(); + } + public Integer getOutTime() { + return (Integer)outSpinner.getValue(); + } + public void setInTime(int k) { + inSpinner.setValue(new Integer(k)); + } + public void setOutTime(int k) { + outSpinner.setValue(new Integer(k)); + } +} + +class Work { + public String name; + public String task; + public String startDate; + public String duration; + public Date beginTime; + + Work() { + beginTime = new Date(); + startDate = beginTime.toString(); + } + public void stopWork() { + Date stopTime = new Date(); + long milliseconds = stopTime.getTime() - beginTime.getTime(); + int minutes = new Long(milliseconds / 1000 / 60).intValue(); + duration = String.valueOf(minutes); + name = project.getTranscriberName(); + task = project.getTranscriberTask(); + } +} + +class Project extends JPanel { + JTextField titleField, mediaField, transcriptField, nameField, taskField; + File transcript = null; + File media = null; + +public String getTitle() { + return titleField.getText(); +} +public void setTitle(String s) { + titleField.setText(s); +} +public String getTranscriberName() { + return nameField.getText(); +} +public void setTranscriberName(String s) { + nameField.setText(s); +} +public String getTranscriberTask() { + return taskField.getText(); +} +public void setTranscriberTask(String s) { + taskField.setText(s); +} +public File getTranscript() { + return transcript; +} +public void setTranscript(File f) { //doesn't actually load or save transcript, just changes changeTranscript label + transcript = f; + if (transcript == null) + transcriptField.setText(""); + else + transcriptField.setText(transcript.getPath()); +} +public File getMedia() { + return media; +} +public void setMedia(File f) { + if (f == null) { + media = f; + mediaField.setText(media.getPath()); + if (player != null) { + player.cmd_stop(); + player.destroy(); + videoFrame.getContentPane().remove(player); + videoFrame.getContentPane().invalidate(); + videoFrame.getContentPane().validate(); + videoFrame.getContentPane().repaint(); + player = null; + videoFrame.setSize(new Dimension(QD.this.getSize().width / 2, 0)); + jtp.remove(tcp); + tcp = null; + actionFrame.setLocation(0,0); + actionFrame.setSize(new Dimension(actionFrame.getSize().width, QD.this.getSize().height)); + } + } + else { + try { + URL url = f.toURL(); + + if (player != null) { + player.cmd_stop(); + player.destroy(); + } + player = new QDPlayer(QD.this, url); + media = f; + mediaField.setText(media.getPath()); + startTimer(); + } catch (MalformedURLException murle) { + murle.printStackTrace(); + } + } +} + +public Project() { + JPanel p = new JPanel(new GridLayout(2,2)); + titleField = new JTextField(); + int preferredHeight = titleField.getPreferredSize().height; + titleField.setPreferredSize(new Dimension(300, preferredHeight)); + mediaField = new JTextField(); + mediaField.setPreferredSize(new Dimension(300, preferredHeight)); + mediaField.setEditable(false); + transcriptField = new JTextField(); + transcriptField.setPreferredSize(new Dimension(300, preferredHeight)); + transcriptField.setEditable(false); + nameField = new JTextField(); + nameField.setPreferredSize(new Dimension(300, preferredHeight)); + taskField = new JTextField(); + taskField.setPreferredSize(new Dimension(300, preferredHeight)); + + Box box = Box.createVerticalBox(); + JPanel p1 = new JPanel(); + p1.add(new JLabel(messages.getString("TitleColon"))); + p1.add(titleField); + JPanel p1b = new JPanel(new BorderLayout()); + p1b.add("East", p1); + p1b.setAlignmentX(Component.RIGHT_ALIGNMENT); + + JPanel p2 = new JPanel(); + p2.add(new JLabel(messages.getString("MediaColon"))); + p2.add(mediaField); + JPanel p2b = new JPanel(new BorderLayout()); + p2b.add("East", p2); + p2b.setAlignmentX(Component.RIGHT_ALIGNMENT); + + JPanel p3 = new JPanel(); + p3.add(new JLabel(messages.getString("TranscriptColon"))); + p3.add(transcriptField); + JPanel p3b = new JPanel(new BorderLayout()); + p3b.add("East", p3); + p3b.setAlignmentX(Component.RIGHT_ALIGNMENT); + + JPanel p4 = new JPanel(); + p4.add(new JLabel(messages.getString("TranscriberName"))); + p4.add(nameField); + JPanel p4b = new JPanel(new BorderLayout()); + p4b.add("East", p4); + p4b.setAlignmentX(Component.RIGHT_ALIGNMENT); + + JPanel p5 = new JPanel(); + p5.add(new JLabel(messages.getString("TranscriberTask"))); + p5.add(taskField); + JPanel p5b = new JPanel(new BorderLayout()); + p5b.add("East", p5); + p5b.setAlignmentX(Component.RIGHT_ALIGNMENT); + + box.add(p1b); + box.add(p2b); + box.add(p3b); + box.add(p4b); + box.add(p5b); + + JPanel pBig = new JPanel(new BorderLayout()); + pBig.add("West", box); + + setLayout(new BorderLayout()); + add("North", pBig); +} +} + +public void changeKeyboard(ActionEvent e) { + DuffPane dp = (DuffPane)pane; + if (e.getActionCommand().equals("wylie")) { + dp.registerKeyboard(); + // project.tName.setupKeyboard(); + // project.tTask.setupKeyboard(); + sharedDP.setupKeyboard(); + sharedDP2.setupKeyboard(); + return; + } + keyboard_url = null; + if (e.getActionCommand().equals("sambhota1")) + keyboard_url = TibetanMachineWeb.class.getResource("sambhota_keyboard_1.ini"); + else if (e.getActionCommand().equals("tcc1")) + keyboard_url = TibetanMachineWeb.class.getResource("tcc_keyboard_1.ini"); + else if (e.getActionCommand().equals("tcc2")) + keyboard_url = TibetanMachineWeb.class.getResource("tcc_keyboard_2.ini"); + dp.registerKeyboard(keyboard_url); +// project.tName.setupKeyboard(); +// project.tTask.setupKeyboard(); + sharedDP.setupKeyboard(); + sharedDP2.setupKeyboard(); +} + +public boolean getSave() { + if (pane.getDocument().getLength() == 0 && speakerTable.getRowCount() == 0) + return true; //nothing to save + + if (project.getTranscript() == null) { + if (project.getMedia() == null) + fileChooser.setSelectedFile(null); + else { + String path = project.getMedia().getPath(); + path = path.substring(0, path.lastIndexOf('.')) + ".xml"; + fileChooser.setSelectedFile(new File(path)); + } + if (fileChooser.showSaveDialog(QD.this) == JFileChooser.APPROVE_OPTION) { + File f = fileChooser.getSelectedFile(); + project.setTranscript(f); + } else + return false; + } + currentWork.stopWork(); + +//change keyboard back to wylie for a second + DuffPane dp = (DuffPane)pane; +if (keyboard_url != null) { + + dp.registerKeyboard(); + // project.tName.setupKeyboard(); + // project.tTask.setupKeyboard(); + sharedDP.setupKeyboard(); + sharedDP.setupKeyboard(); +} + + + org.jdom.Element title = new org.jdom.Element("title"); + title.setText(project.getTitle().trim()); + + org.jdom.Element media = new org.jdom.Element("mediafile"); + if (project.getMedia() == null) + media.setText(""); + else { + String tPath = project.getTranscript().getPath(); + String mPath = project.getMedia().getPath(); + if (tPath.substring(0, tPath.lastIndexOf(File.separatorChar)).equals(mPath.substring(0, mPath.lastIndexOf(File.separatorChar)))) + media.setText(mPath.substring(mPath.lastIndexOf(File.separatorChar)+1)); + else + media.setText(mPath); + } + + org.jdom.Element speakers = new org.jdom.Element("speakers"); + SpeakerData sd = (SpeakerData)speakerTable.getModel(); + java.util.List allSpeakers = sd.getSpeakers(); + Iterator spIter = allSpeakers.iterator(); + while (spIter.hasNext()) { + Speaker sp = (Speaker)spIter.next(); + org.jdom.Element speaker = new org.jdom.Element("speaker"); + speaker.setAttribute("iconid", String.valueOf(sp.getIconID())); + speaker.setText(sp.getName()); + speakers.addContent(speaker); + } + + org.jdom.Element works = new org.jdom.Element("workhistory"); + Iterator wkIter = work.iterator(); + while (wkIter.hasNext()) { + Work wkUnit = (Work)wkIter.next(); + org.jdom.Element wk = new org.jdom.Element("work"); + org.jdom.Element name = new org.jdom.Element("name"); + name.setText(wkUnit.name); + org.jdom.Element task = new org.jdom.Element("task"); + task.setText(wkUnit.task); + org.jdom.Element date = new org.jdom.Element("start"); + date.setText(wkUnit.startDate); + org.jdom.Element duration = new org.jdom.Element("duration"); + duration.setText(wkUnit.duration); + wk.addContent(name); + wk.addContent(task); + wk.addContent(date); + wk.addContent(duration); + works.addContent(wk); + } + + org.jdom.Element meta = new org.jdom.Element("metadata"); + meta.addContent(title); + meta.addContent(media); + meta.addContent(speakers); + meta.addContent(works); + + org.jdom.Element text = new org.jdom.Element("text"); + TibetanDocument doc = (TibetanDocument)pane.getDocument(); + ImageIcon[] icons = sd.getSpeakerIcons(); + int lastPoint = 0; + int k; + for (k=0; k 0) + dp.setText(""); + java.util.List textContent = text.getContent(); + ImageIcon[] icons = sd.getSpeakerIcons(); + Iterator textIter = textContent.iterator(); + boolean wasLastComponent = true; + while (textIter.hasNext()) { + Object nextContent = textIter.next(); + if (nextContent instanceof org.jdom.Text) { + String wylie = ((org.jdom.Text)nextContent).getText(); + dp.toTibetanMachineWeb(wylie, tDoc.getLength()); + wasLastComponent = false; + } + else if (nextContent instanceof org.jdom.Element) { + org.jdom.Element e = (org.jdom.Element)nextContent; + dp.setCaretPosition(tDoc.getLength()); + if (e.getName().equals("time")) { +/* if (!wasLastComponent) + try { + tDoc.insertString(tDoc.getLength(), "\n", null); + } catch (BadLocationException ble) { + ble.printStackTrace(); + } +*/ + new TimePoint(dp, clockIcon, Integer.valueOf(e.getAttributeValue("t"))); + wasLastComponent = true; + } + else if (e.getName().equals("who")) { +/* if (!wasLastComponent) + try { + tDoc.insertString(tDoc.getLength(), "\n", null); + } catch (BadLocationException ble) { + ble.printStackTrace(); + } +*/ + dp.insertComponent(new JLabel(" ", icons[Integer.parseInt(e.getAttributeValue("id"))], SwingConstants.LEFT)); + wasLastComponent = true; + } + } + } + currentWork = new Work(); + work.add(currentWork); + +if (keyboard_url != null) { + dp.registerKeyboard(keyboard_url); + // project.tName.setupKeyboard(); + // project.tTask.setupKeyboard(); + sharedDP.setupKeyboard(); + sharedDP2.setupKeyboard(); +} + +project.setTranscript(t); + + return true; + } catch (JDOMException jdome) { + jdome.printStackTrace(); + return false; + } +} + +class SpeakerManager extends JPanel { + JPanel top; + boolean isEditable; + +//public JPanel getSpeakerManager(final SpeakerTable sTable) { + +SpeakerManager(final SpeakerTable sTable) { + + setLayout(new BorderLayout()); + + JButton addButton = new JButton(messages.getString("Add")); + addButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + SpeakerData sd = (SpeakerData)sTable.getModel(); + JPanel p0 = new JPanel(new GridLayout(0,1)); + JPanel p1 = new JPanel(); + p1.add(new JLabel(messages.getString("FaceColon") + " ")); + JComboBox combo = new JComboBox(sd.getSpeakerIcons()); + p1.add(combo); + JPanel p2 = new JPanel(); + p2.add(new JLabel(messages.getString("NameColon") + " ")); + sharedDP.setText(""); + sharedDP.setPreferredSize(new Dimension(250,50)); + p2.add(sharedDP); + p0.add(p1); + p0.add(p2); + if (JOptionPane.showConfirmDialog(SpeakerManager.this, p0, messages.getString("EnterNewSpeakerInfo"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null) == JOptionPane.OK_OPTION) { + TibetanDocument doc = (TibetanDocument)sharedDP.getDocument(); + sd.addSpeaker(new Speaker(combo.getSelectedIndex(), doc.getWylie())); + } + } + }); + JButton editButton = new JButton(messages.getString("Edit")); + editButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int row = sTable.getSelectedRow(); + if (row == -1) + return; + SpeakerData sd = (SpeakerData)sTable.getModel(); + Speaker sp = sd.getSpeakerAt(row); + JPanel p0 = new JPanel(new GridLayout(0,1)); + JPanel p1 = new JPanel(); + p1.add(new JLabel(messages.getString("FaceColon") + " ")); + JComboBox combo = new JComboBox(sd.getSpeakerIcons()); + combo.setSelectedIndex(sp.getIconID()); + ImageIcon[] icons = sd.getSpeakerIcons(); + ImageIcon oldIcon = icons[sp.getIconID()]; + p1.add(combo); + JPanel p2 = new JPanel(); + p2.add(new JLabel(messages.getString("NameColon") + " ")); + sharedDP.setText(""); + sharedDP.setPreferredSize(new Dimension(250,50)); + sharedDP.toTibetanMachineWeb(sp.getName(), 0); + p2.add(sharedDP); + p0.add(p1); + p0.add(p2); + if (JOptionPane.showConfirmDialog(SpeakerManager.this, p0, messages.getString("EditSpeakerInfo"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null) == JOptionPane.OK_OPTION) { + sd.setValueAt(new Integer(combo.getSelectedIndex()), row, 0); + TibetanDocument doc = (TibetanDocument)sharedDP.getDocument(); + sd.setValueAt(doc.getWylie(), row, 1); + if (!oldIcon.equals(icons[combo.getSelectedIndex()])) { + DefaultStyledDocument doc2 = (DefaultStyledDocument)pane.getDocument(); + int k = pane.getCaretPosition(); + for (int i=0; i -1 && JOptionPane.showConfirmDialog(SpeakerManager.this, messages.getString("SureDeleteSpeaker"), messages.getString("Warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) { + SpeakerData sd = (SpeakerData)sTable.getModel(); + Speaker sp = sd.getSpeakerAt(row); + ImageIcon[] icons = sd.getSpeakerIcons(); + DefaultStyledDocument doc = (DefaultStyledDocument)pane.getDocument(); + int k2 = doc.getLength(); + for (int i=0; i 0) { + DefaultStyledDocument doc = (DefaultStyledDocument)tp.getDocument(); + AttributeSet attr = doc.getCharacterElement(pos-1).getAttributes(); + Component comp; + if (null != (comp = StyleConstants.getComponent(attr))) + if (comp instanceof JLabel) { + JLabel l = (JLabel)comp; + ImageIcon ic = (ImageIcon)l.getIcon(); + if (iconMap.containsKey(ic)) { + Speaker sp = (Speaker)iconMap.get(ic); + int k = speakers.indexOf(sp) + 1; + if (k == speakers.size()) + k=0; + try { + tp.getDocument().remove(pos-1, 1); + sp = (Speaker)speakers.get(k); + tp.insertComponent(new JLabel(" ", spIcon[sp.getIconID()], SwingConstants.LEFT)); + return; + } catch (BadLocationException ble) { + ble.printStackTrace(); + } + } + } + } + Speaker sp = (Speaker)speakers.get(0); + tp.insertComponent(new JLabel(" ", spIcon[sp.getIconID()], SwingConstants.LEFT)); + } + }; + KeyStroke insertSpeakerKey = KeyStroke.getKeyStroke(KeyEvent.VK_S, Event.ALT_MASK); + keymap.addActionForKeyStroke(insertSpeakerKey, insertSpeakerAction); + } + public boolean addSpeaker(Speaker speaker) { + if (iconMap.containsKey(spIcon[speaker.getIconID()])) + return false; + else { + iconMap.put(spIcon[speaker.getIconID()], speaker); + speakers.add(speaker); + fireTableRowsInserted(speakers.size()-1, speakers.size()-1); + return true; + } + } + public Speaker getSpeakerAt(int row) { + if (row > -1 && row < speakers.size()) + return ((Speaker)speakers.get(row)); + return null; + } + public java.util.List getSpeakers() { + return speakers; + } + public ImageIcon[] getSpeakerIcons() { + return spIcon; + } + public Speaker getSpeakerForIcon(Icon icon) { + if (iconMap.containsKey(icon)) + return (Speaker)iconMap.get(icon); + else + return null; + } + public void removeAllSpeakers() { + speakers.clear(); + iconMap.clear(); + } + public void removeSpeaker(int row) + { + if (row > -1 && row < speakers.size()) + { + Speaker sp = getSpeakerAt(row); + iconMap.remove(spIcon[sp.getIconID()]); + speakers.remove(row); + fireTableRowsDeleted(row,row); + } + } + public int getRowCount() + { + return speakers.size(); + } + public int getColumnCount() + { + return 2; + } + public Class getColumnClass(int c) + { + return getValueAt(0, c).getClass(); + } + public Object getValueAt(int row, int column) + { + Speaker sp = (Speaker)speakers.get(row); + if (column == 0) + return spIcon[sp.getIconID()]; + try { //otherwise column 1, the speaker name + return TibetanDocument.getTibetanMachineWeb(sp.getName().trim()); + } catch (InvalidWylieException iwe) { + iwe.printStackTrace(); + return null; + } + } + public void setValueAt(Object object, int row, int column) { + Speaker sp = (Speaker)speakers.get(row); + if (column == 0 && object instanceof Integer) { + Integer bigInt = (Integer)object; + int k = bigInt.intValue(); + if (!iconMap.containsKey(spIcon[k])) { + iconMap.remove(spIcon[sp.getIconID()]); + iconMap.put(spIcon[k], sp); + sp.setIconID(k); + } + } else if (object instanceof String) { + String wylie = (String)object; + sp.setName(wylie); + } + fireTableCellUpdated(row, column); + } +} + +class SpeakerTable extends JTable +{ + private TableCellRenderer duffRenderer, normalRenderer; + private SpeakerData speakerData; + + public SpeakerTable(SpeakerData speakerData) + { + this.speakerData = speakerData; + this.setModel(this.speakerData); + this.setRowHeight(55); + this.setColumnSelectionAllowed(false); + this.setRowSelectionAllowed(true); + + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + TableColumnModel tcm = this.getColumnModel(); + duffRenderer = new DuffCellRenderer(); + + TableColumn tc = tcm.getColumn(0); + tc.setResizable(false); + tc.setMaxWidth(60); + tc.setMinWidth(60); + tc.setHeaderValue(messages.getString("Face")); + + tc = tcm.getColumn(1); + tc.setCellRenderer(duffRenderer); + tc.setHeaderValue(messages.getString("Name")); + } +} + +public JMenuBar getTextMenuBar() { + JMenu modeMenu = new JMenu(messages.getString("Mode")); + JRadioButton editButton = new JRadioButton(messages.getString("Edit")); + JRadioButton viewButton = new JRadioButton(messages.getString("ReadOnly")); + editButton.setActionCommand("edit"); + viewButton.setActionCommand("view"); + RadioListener l = new RadioListener(); + editButton.addActionListener(l); + viewButton.addActionListener(l); + editButton.setSelected(true); + ButtonGroup bg = new ButtonGroup(); + bg.add(editButton); + bg.add(viewButton); + JPanel buttons = new JPanel(new GridLayout(0,1)); + buttons.add(editButton); + buttons.add(viewButton); + modeMenu.add(buttons); + + JMenu viewMenu = new JMenu(messages.getString("View")); + JMenuItem previousItem = new JMenuItem(messages.getString("Previous")); + JMenuItem currentItem = new JMenuItem(messages.getString("Current")); + JMenuItem nextItem = new JMenuItem(messages.getString("Next")); + JMenuItem suppressTimesItem = new JMenuItem(messages.getString("SuppressTimes")); + viewMenu.add(previousItem); + viewMenu.add(currentItem); + viewMenu.add(nextItem); + viewMenu.addSeparator(); + viewMenu.add(suppressTimesItem); + + JMenu searchMenu = new JMenu(messages.getString("Search")); + JMenuItem findTextItem = new JMenuItem(messages.getString("FindText")); + findTextItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + findText(); + } + }); + + JMenuItem replaceTextItem = new JMenuItem(messages.getString("ReplaceText")); + replaceTextItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + replaceText(); + } + }); + + JMenuItem findspeakerItem = new JMenuItem(messages.getString("FindSpeaker")); + JMenuItem findtimeItem = new JMenuItem(messages.getString("FindTime")); + searchMenu.add(findTextItem); + searchMenu.add(replaceTextItem); + searchMenu.addSeparator(); + searchMenu.add(findspeakerItem); + searchMenu.add(findtimeItem); + + editMenu = new JMenu(messages.getString("Edit")); + JMenuItem cutItem = new JMenuItem(messages.getString("Cut")); + JMenuItem copyItem = new JMenuItem(messages.getString("Copy")); + JMenuItem pasteItem = new JMenuItem(messages.getString("Paste")); + JMenuItem selectallItem = new JMenuItem(messages.getString("SelectAll")); + JMenuItem insertspeakerItem = new JMenuItem(messages.getString("InsertSpeaker")); + JMenuItem insertonetimeItem = new JMenuItem(messages.getString("InsertOneTime")); + JMenuItem inserttwotimesItem = new JMenuItem(messages.getString("InsertTwoTimes")); + editMenu.add(cutItem); + editMenu.add(copyItem); + editMenu.add(pasteItem); + editMenu.add(selectallItem); + editMenu.addSeparator(); + editMenu.add(insertspeakerItem); + editMenu.add(insertonetimeItem); + editMenu.add(inserttwotimesItem); + + JMenuBar bar = new JMenuBar(); + bar.add(modeMenu); + bar.add(viewMenu); + bar.add(searchMenu); + bar.add(editMenu); + return bar; +} + +public int findNextText(int startPos, TibetanDocument sourceDoc, TibetanDocument findDoc) { + if (startPos<0 || startPos>sourceDoc.getLength()-1) + return -1; + +try { + + AttributeSet firstAttr = findDoc.getCharacterElement(0).getAttributes(); + String firstFontName = StyleConstants.getFontFamily(firstAttr); + char firstSearchChar = findDoc.getText(0,1).charAt(0); + + boolean match = false; + for (int i=startPos; i0) { //found!! so highlight text and seek video + if (startingOver && i>pos.getOffset()-1) + break; + pane.setSelectionStart(i); + pane.setSelectionEnd(i+findDoc.getLength()); + if (JOptionPane.showInternalConfirmDialog(textFrame, messages.getString("FindNextYesNo"), null, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null) != JOptionPane.YES_OPTION) + break; + i++; + } + if (i<0 || i==doc.getLength() && pos.getOffset()>0) { //reached end of document - start over? + if (startingOver || pos.getOffset()==0) + break; + if (JOptionPane.showInternalConfirmDialog(textFrame, messages.getString("StartFromBeginning"), null, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null) != JOptionPane.YES_OPTION) + break; + i=0; + startingOver = true; + } + } + } catch (BadLocationException ble) { + ble.printStackTrace(); + } + + } +} + +public void replaceText() { + //get input from user on what to search for + JPanel p0 = new JPanel(new GridLayout(0,1)); + JPanel p2 = new JPanel(); + p2.add(new JLabel(messages.getString("FindWhat") + " ")); + if (findDoc == null) { + sharedDP.setText(""); + findDoc = (TibetanDocument)sharedDP.getDocument(); + } else + sharedDP.setDocument(findDoc); + sharedDP.setPreferredSize(new Dimension(250,50)); + p2.add(sharedDP); + JPanel p3 = new JPanel(); + p3.add(new JLabel(messages.getString("ReplaceWith") + " ")); + if (replaceDoc == null) { + sharedDP2.setText(""); + replaceDoc = (TibetanDocument)sharedDP2.getDocument(); + } else + sharedDP2.setDocument(replaceDoc); + sharedDP2.setPreferredSize(new Dimension(250,50)); + p3.add(sharedDP2); + + p0.add(p2); + p0.add(p3); + + if (JOptionPane.showConfirmDialog(textFrame, p0, messages.getString("FindReplaceHeading"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null) == JOptionPane.OK_OPTION) { + + try { + java.util.List replaceDCs = new ArrayList(); + for (int k=0; k0) { //found!! so highlight text and seek video + if (startingOver && i>pos.getOffset()-1) + break; + if (replaceAll) { + doc.remove(i,findDoc.getLength()); + doc.insertDuff(i,dd); + } else { + pane.setSelectionStart(i); + pane.setSelectionEnd(i+findDoc.getLength()); + String choice = (String)JOptionPane.showInternalInputDialog(textFrame, messages.getString("ReplaceQ"), null, JOptionPane.PLAIN_MESSAGE, null, replaceOptions, replaceOptions[0]); + if (choice == null) //cancel, so stop searching + break outer; + if (choice.equals(replaceOptions[0])) { //replace + doc.remove(i,findDoc.getLength()); + doc.insertDuff(i,dd); + } + else if (choice.equals(replaceOptions[1])) { //replace all + doc.remove(i,findDoc.getLength()); + doc.insertDuff(i,dd); + replaceAll = true; + } + } + i++; + } + if (i<0 || i==doc.getLength() && pos.getOffset()>0) { //reached end of document - start over? + if (startingOver || pos.getOffset()==0) + break; + if (JOptionPane.showInternalConfirmDialog(textFrame, messages.getString("StartFromBeginning"), null, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null) != JOptionPane.YES_OPTION) + break; + i=0; + startingOver = true; + } + } + } catch (BadLocationException ble) { + ble.printStackTrace(); + } + + } +} + +class RadioListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("edit")) { + //make the text pane editable + pane.setEditable(true); + + //reinstate the edit menu to the pane + textFrame.getJMenuBar().add(editMenu); + textFrame.invalidate(); + textFrame.validate(); + textFrame.repaint(); + + //add speaker-edit features + spm.setEditable(true); + + //add the time-coding tab + if (tcp != null) + jtp.addTab(messages.getString("TimeCoding"), tcp); + + //make the project details editable + if (project != null) +// project.setEditable(true); + + jtp.addTab(messages.getString("ProjectDetails"), project); + } + else if (e.getActionCommand().equals("view")) { + //make the text pane non-editable + pane.setEditable(false); + + //remove the edit menu from the pane + textFrame.getJMenuBar().remove(editMenu); + textFrame.invalidate(); + textFrame.validate(); + textFrame.repaint(); + + //remove speaker-edit features + spm.setEditable(false); + + //remove the time-coding tab + if (tcp != null) + jtp.remove(tcp); + + //make the project tab non-editable + if (project != null) + jtp.remove(project); +// project.setEditable(false); + } + } +} + + private class XMLFilter extends javax.swing.filechooser.FileFilter { + // accepts all directories and all savant files + + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + + String fName = f.getName(); + int i = fName.lastIndexOf('.'); + + if (i < 0) + return false; + + else { + String ext = fName.substring(i+1).toLowerCase(); + + if (ext.equals("xml")) + return true; + else + return false; + } + } + + //the description of this filter + public String getDescription() { + return "Extensible Markup Language (.xml)"; + } + } +} diff --git a/org/thdl/quilldriver/QDPlayer.java b/org/thdl/quilldriver/QDPlayer.java new file mode 100644 index 0000000..5c061df --- /dev/null +++ b/org/thdl/quilldriver/QDPlayer.java @@ -0,0 +1,405 @@ +package org.thdl.quilldriver; + +import java.util.*; +import java.net.*; +import javax.media.*; +import java.awt.*; +import javax.swing.*; +import javax.swing.event.*; + +/*-----------------------------------------------------------------------*/ +public class QDPlayer extends Panel implements ControllerListener +{ + private EventListenerList listenerList = new EventListenerList(); + + public URL mediaURL; + + private Vector orderStartID, orderEndID; + private Stack pileStart, pileEnd; + private Hashtable hashStart, hashEnd; + + private Player player = null; + private Component visualComponent = null; + private Component controlComponent = null; + private Panel panel = null; + private JPanel vPanel = null; + + private Container parent = null; + + private java.util.Timer timer = null; + private Time stopTime = null; + private Time pauseTime = null; + private boolean stillLoadingVideo = false; + private boolean isMediaAudio = false; + private boolean isSized = false; + + private Float to = null; +/*-----------------------------------------------------------------------*/ + public QDPlayer(Container p, URL sound) { + parent = p; + makeMedia(sound); + } + public void makeMedia(URL sound) { + if (mediaURL != null) { + cmd_stop(); + destroy(); + } + mediaURL = sound; + start(); + } + public URL getURL() { + return mediaURL; + } + public void destroy() { + player.close(); + } + public void start() { + openPlayer(); + } + public Component popVisualComponent() + { + vPanel.remove(visualComponent); + invalidate(); + validate(); + repaint(); + return visualComponent; + } + public void restoreVisualComponent() + { + vPanel.add("Center", visualComponent); + invalidate(); + validate(); + repaint(); + } + public Component getVisualComponent() + { + return visualComponent; + } + public Component getControlComponent() + { + return controlComponent; + } +/*-----------------------------------------------------------------------*/ + public boolean cmd_isSized() { + return isSized; + } + public boolean cmd_isRealized() { + return player.getState() == Controller.Realized; + } + public String cmd_firstS() { + return (String)orderStartID.elementAt(0); + } + public boolean cmd_stop() { + if (player == null) + return false; + try { + player.stop(); + return true; + } catch (NotRealizedError err) { + System.out.println("NotRealizedError"); + return false; + } + } + public boolean cmd_isID(String theID) { + System.out.println(hashStart.containsKey(theID)); + return hashStart.containsKey(theID); + } + public boolean cmd_play() { + if (stillLoadingVideo || player == null) + return false; + if (player.getState() == Controller.Started) + return true; + + if (pauseTime == null) + player.setMediaTime(new Time(0.0)); + else + player.setMediaTime(pauseTime); + if (player.getTargetState() < Player.Started) { + player.setStopTime(Clock.RESET); + player.prefetch(); + } + player.start(); + return true; + } + public boolean cmd_playFrom(Integer from) { + if (stillLoadingVideo) + return false; + if (play(from, null)) + return true; + else + return false; + } + public boolean cmd_play(Integer from, Integer to) { + if (stillLoadingVideo) + return false; + if (play(from, to)) + return true; + else + return false; + } +/*-----------------------------------------------------------------------*/ + public int getLastTime() { + Time t = player.getDuration(); + long l = t.getNanoseconds(); + return new Long(l / 1000000).intValue(); + } + public int when() { + if (player == null) + return -1; + if (player.getState() < Controller.Realized) + return -1; + long currTime = player.getMediaNanoseconds(); + return new Long(currTime / 1000000).intValue(); + } + private boolean play(Integer from, Integer to) { + if (player == null) + return false; + + final Time startTime = new Time(from.longValue() * 1000000); + try { + if (player.getState() == Controller.Started) + player.stop(); + while (player.getState() == Controller.Unrealized) + ; + +// player.stop(); + if (to == null) { + stopTime = null; + player.setStopTime(Clock.RESET); + } else { + stopTime = new Time(to.longValue() * 1000000); + player.setStopTime(stopTime); + } + player.setMediaTime(startTime); + player.prefetch(); + player.start(); + return true; + } catch(NotRealizedError err) { + System.out.println("NotRealizedError"); + return false; + } + } +/*-----------------------------------------------------------------------*/ + public void openPlayer() { + try { + player = Manager.createPlayer(mediaURL); + player.addControllerListener(this); + } catch (javax.media.NoPlayerException e) { + System.err.println("noplayer exception"); + e.printStackTrace(); + return; + } catch (java.io.IOException ex) { + System.err.println("IO exception"); + ex.printStackTrace(); + return; + } + if (player != null) + player.realize(); + } + public synchronized void controllerUpdate(ControllerEvent event) { + if (player == null) + return; + if (event instanceof RealizeCompleteEvent) { + System.out.println("received RealizeCompleteEvent event"); + if (visualComponent == null) { + if (panel == null) { + setLayout(new GridLayout(1,1)); + vPanel = new JPanel(); + vPanel.setLayout( new BorderLayout() ); + if ((visualComponent = player.getVisualComponent())!= null) + vPanel.add("Center", visualComponent); + else { + isMediaAudio = true; + stillLoadingVideo = false; + } + if (!stillLoadingVideo) + { + if ((controlComponent = player.getControlPanelComponent()) != null) { + if (visualComponent == null) //no video + vPanel.setPreferredSize(new Dimension(400,25)); + vPanel.add("South", controlComponent); + } + } + add(vPanel); + } + } + parent.invalidate(); + parent.validate(); + parent.repaint(); + isSized = true; + if (stillLoadingVideo) + player.start(); + } else if (event instanceof StartEvent) { + StartEvent se = (StartEvent)event; + Time t = se.getMediaTime(); + long longt = t.getNanoseconds(); + Float from = new Float(longt); + float f = (from.floatValue() / 1000000000); + from = new Float(f); + t = player.getStopTime(); + longt = t.getNanoseconds(); + to = new Float(longt); + f = (to.floatValue() / 1000000000); + to = new Float(f); + if (timer != null) + { + timer.cancel(); + timer = null; + } + timer = new java.util.Timer(true); + timer.schedule(new TimerTask() { + public void run() { + //this is specifically for the MPG stop time bug + if (stopTime != null) + if (player.getMediaTime().getNanoseconds() > stopTime.getNanoseconds()) + player.stop(); + }}, 0, 15); + } else if (event instanceof StopEvent) { + pauseTime = player.getMediaTime(); + + + /*messy problems require messy solutions: + if the slider is present, dragging it while playing creates + a RestartingEvent, and if I set the media time here it messes up + and barely plays at all (maybe because it cancels the previously + set media time? - I don't know). + + but it seems that if you press the play/pause button on the + control widget, then you need to set the media time upon stop + (probably because of problem noted below, namely that you get + weird results if you do player.start() without setting the media + time.*/ + + if (!(event instanceof RestartingEvent)) + player.setMediaTime(pauseTime); + +// player.setStopTime(Clock.RESET); + stopTime = null; + + System.out.println("received StopEvent"); + + if (timer != null) + { + timer.cancel(); + timer = null; + } + if (stillLoadingVideo) + { + System.out.println("received EndOfMediaEvent"); + stillLoadingVideo = false; + player.stop(); + if ((controlComponent = player.getControlPanelComponent()) != null) { + if (visualComponent == null) //no video + vPanel.setPreferredSize(new Dimension(400,25)); + vPanel.add("South", controlComponent); + } + parent.invalidate(); + parent.validate(); + parent.repaint(); + } + } else if ( event instanceof CachingControlEvent) { + CachingControlEvent e = (CachingControlEvent) event; + System.out.println("got CachingControlEvent: " + e); + if (!isMediaAudio) + stillLoadingVideo = true; + } else if (event instanceof ControllerErrorEvent) { + player = null; + System.err.println("*** ControllerErrorEvent *** " + ((ControllerErrorEvent)event).getMessage()); + } else if (event instanceof PrefetchCompleteEvent) { + if (panel != null) { + panel.invalidate(); + } + parent.invalidate(); + parent.validate(); + parent.repaint(); + } + } +}; + +/* + +After pause the MPEG video and playing it again it gets faster +Author: vladshtr +In Reply To: After pause the MPEG video and playing it again it gets faster +Mar 1, 2001 6:25 PM + +Reply 1 of 1 + + +The problem is in the setting the Meida time. + +The safety way is to always set new media time with the +following method: setMediaTime(Time time); .... if you want to +use it after +-player.stop(); used as real stop you can use setMediaTime(new +Time(0.0)); +-player.stop(); used as real pause you have to use the +combination: +player.stop(); +Time currentTime = player.getMediaTime(); +//........ +player.setMediaTime(currentTime); +player.start(); + + +Re: (urgent) when you pause and resume, video plays at rate > 1 +Author: seertenedos +In Reply To: (urgent) when you pause and resume, video plays at rate > 1 +Aug 11, 2002 11:36 PM + +Reply 1 of 1 + + +I found a solution for this problem for those that are interested. + +what you have to do is store the time just before you pause and then set the +time just before you play. here is a copy of my pause and play methods + +// Start the player +private void play() { +if (player != null) +{ +if (pauseTime != null) +{ +player.setMediaTime(pauseTime); +} +if (player.getTargetState() < Player.Started) +player.prefetch(); +player.start(); +} +} + +// Pause the player +private void pause() { +if (player != null) +pauseTime = player.getMediaTime(); +player.stop(); +} + + +that should solve your pause play problems! + +> The problem is below. It seems quite a few people are +> having this problem but i have not seen a solution +> around. I really need a solution to this problem as +> the whole point of my application is that it plays +> divx and mpeg videos. At the moment i have divx +> movies playing through the mpeg demuxer as the avi one +> stuffed up the audio. I think that is the reason it +> affects both divx and mpeg. My application is due in +> one week and my client is not going to be very happy +> if this problem happens every time they pause then +> play the video. +> The player is for divx movies. If anyone knows how to +> solve this problem or how to make it so you can pause +> and resume divx movies please respond. +> +> Pause and Resume playback. +> The video plays at a high rate and there is no audio. +> Problem doesn't appear while seeking. +> +> +> +> + +*/ \ No newline at end of file diff --git a/org/thdl/quilldriver/QDShell.java b/org/thdl/quilldriver/QDShell.java new file mode 100644 index 0000000..0c33cb8 --- /dev/null +++ b/org/thdl/quilldriver/QDShell.java @@ -0,0 +1,159 @@ +package org.thdl.quilldriver; + +import java.io.*; +import java.awt.*; +import java.net.*; +import java.util.*; +import javax.swing.*; +import java.awt.event.*; +import javax.swing.text.*; +import javax.swing.text.rtf.*; + +public class QDShell extends JFrame { + ResourceBundle messages = null; + QD qd = null; + +public static void main(String[] args) { + try { + PrintStream ps = new PrintStream(new FileOutputStream("qd.log")); + System.setErr(ps); + System.setOut(ps); + } + catch (Exception e) { + } + + Locale locale; + + if (args.length == 2) { + locale = new Locale(new String(args[0]), new String(args[1])); + Locale[] locales = Locale.getAvailableLocales(); + for (int k=0; k 0) { + int num = getMinusStart(saveOrder); + orderStartID.addElement(saveOrder.elementAt(num)); + saveOrder.removeElementAt(num); + } + saveOrder = new Vector(); + for (Enumeration e = hashEnd.keys() ; e.hasMoreElements() ;) { + Object o = e.nextElement(); + saveOrder.addElement(o); + } + orderEndID = new Vector(); + while (saveOrder.size() > 0) { + int num = getMinusEnd(saveOrder); + orderEndID.addElement(saveOrder.elementAt(num)); + saveOrder.removeElementAt(num); + } + } + public void destroy() { + player.close(); + } + public void stop() { + player.stop(); + player.deallocate(); + } + public void start() { + openPlayer(); + } + private int getMinusStart(Vector v) { + int index = 0; + String first = (String)v.elementAt(index); + Float minus = (Float)hashStart.get(first); + for (int i=0;i f.floatValue()) { + minus = f; + index = i; + } + } + return index; + } + private int getMinusEnd(Vector v) { + int index = 0; + String first = (String)v.elementAt(index); + Float minus = (Float)hashEnd.get(first); + for (int i=0;i f.floatValue()) { + minus = f; + index = i; + } + } + return index; + } + public void addAnnotationPlayer(AnnotationPlayer ap) + { + listenerList.add(AnnotationPlayer.class, ap); + } + public void removeAnnotationPlayer(AnnotationPlayer ap) + { + listenerList.remove(AnnotationPlayer.class, ap); + } + public Component popVisualComponent() + { + vPanel.remove(visualComponent); + invalidate(); + validate(); + repaint(); + return visualComponent; + } + public void restoreVisualComponent() + { + vPanel.add("Center", visualComponent); + invalidate(); + validate(); + repaint(); + } + public Component getVisualComponent() + { + return visualComponent; + } + public Component getControlComponent() + { + return controlComponent; + } +/*-----------------------------------------------------------------------*/ + public boolean cmd_isSized() { + return isSized; + } + public boolean cmd_isRealized() { + return player.getState() == Controller.Realized; + } + public String cmd_firstS() { + return (String)orderStartID.elementAt(0); + } + public boolean cmd_stop() { + if (player == null) + return false; + try { + player.stop(); + return true; + } catch (NotRealizedError err) { + System.out.println("NotRealizedError"); + return false; + } + } + public boolean cmd_isID(String theID) { + System.out.println(hashStart.containsKey(theID)); + return hashStart.containsKey(theID); + } + public boolean cmd_playFrom(String fromID) { + if (stillLoadingVideo) + return false; + + Float from = (Float)hashStart.get(fromID); +// String toID = (String)orderEndID.elementAt(orderEndID.size()-1); +// Float to = (Float)hashEnd.get(toID); + + if (play(from, null)) + return true; + else + return false; + } + + public boolean cmd_playS(String fromID) { + if (stillLoadingVideo) + return false; + + Float from = (Float)hashStart.get(fromID); + Float to = (Float)hashEnd.get(fromID); + if (play(from, to)) + return true; + else + return false; + } + public void cmd_nextEvent() { + Float when = new Float(when()); + if (!pileStart.empty()) { + String id = (String)pileStart.peek(); + Float f = (Float)hashStart.get(id); + if (when.floatValue() >= f.floatValue()) { + id = (String)pileStart.pop(); + fireStartAnnotation(id); + } + } + if (!pileEnd.empty()) { + String id = (String)pileEnd.peek(); + Float f = (Float)hashEnd.get(id); + if (when.floatValue() >= f.floatValue()) { + id = (String)pileEnd.pop(); + fireStopAnnotation(id); + } + } + } +/*-----------------------------------------------------------------------*/ + private void vide_Pile() { + while (!pileEnd.empty()) { //vider la pile des items qui ne sont pas + String id = (String)pileEnd.pop(); //encore fini + if (pileStart.search(id) == -1) { + fireStopAnnotation(id); + } + } + } + private void remplisPileStart(Float start, Float end) { + vide_Pile(); + pileStart.removeAllElements(); + pileEnd.removeAllElements(); + for (int i=orderEndID.size()-1; i!=-1; i--) { + String id = (String)orderEndID.elementAt(i); + Float f = (Float)hashEnd.get(id); + if ((f.floatValue() > start.floatValue()) && (f.floatValue() <= end.floatValue())) { + pileEnd.push(id); + } + } + for (int i=orderStartID.size()-1; i!=-1; i--) { + String id = (String)orderStartID.elementAt(i); + Float f = (Float)hashStart.get(id); + if ((f.floatValue() >= start.floatValue()) && (f.floatValue() < end.floatValue())) { + pileStart.push(id); + } + } + } + private void fireStartAnnotation(String id) + { + //see javadocs on EventListenerList for how following array is structured + Object[] listeners = listenerList.getListenerList(); + + for (int i = listeners.length-2; i>=0; i-=2) + { + if (listeners[i]==AnnotationPlayer.class) + ((AnnotationPlayer)listeners[i+1]).startAnnotation(id); + } + } + private void fireStopAnnotation(String id) + { + //see javadocs on EventListenerList for how following array is structured + Object[] listeners = listenerList.getListenerList(); + + for (int i = listeners.length-2; i>=0; i-=2) + { + if (listeners[i]==AnnotationPlayer.class) + ((AnnotationPlayer)listeners[i+1]).stopAnnotation(id); + } + } +/*-----------------------------------------------------------------------*/ + private String when() { + if (player == null) + return "-1"; + if (player.getState() != Controller.Started) + return "-1"; + long currTime = player.getMediaNanoseconds(); + Float time = new Float(currTime); + float f = (time.floatValue() / 1000000000); + return Float.toString(f); + } + private boolean play(Float from, Float to) { + if (player == null) + return false; + final Time startTime = new Time((long)(from.floatValue() * 1000000000)); + try { + if (player.getState() == Controller.Started) + player.stop(); + while (player.getState() == Controller.Unrealized) + ; +// player.stop(); + if (to == null) { + stopTime = null; + player.setStopTime(Clock.RESET); + } else { + stopTime = new Time((long)(to.floatValue() * 1000000000)); + player.setStopTime(stopTime); + } + player.setMediaTime(startTime); + player.prefetch(); + player.start(); + return true; + } catch(NotRealizedError err) { + System.out.println("NotRealizedError"); + return false; + } + } +/*-----------------------------------------------------------------------*/ + public void openPlayer() { + try { + player = Manager.createPlayer(mediaURL); + player.addControllerListener(this); + } catch (javax.media.NoPlayerException e) { + System.err.println("noplayer exception"); + e.printStackTrace(); + return; + } catch (java.io.IOException ex) { + System.err.println("IO exception"); + ex.printStackTrace(); + return; + } + if (player != null) + player.realize(); + } + public synchronized void controllerUpdate(ControllerEvent event) { + if (player == null) + return; + if (event instanceof RealizeCompleteEvent) { + System.out.println("received RealizeCompleteEvent event"); + if (visualComponent == null) { + if (panel == null) { + setLayout(new GridLayout(1,1)); + vPanel = new JPanel(); + vPanel.setLayout( new BorderLayout() ); + if ((visualComponent = player.getVisualComponent())!= null) + vPanel.add("Center", visualComponent); + else { + isMediaAudio = true; + stillLoadingVideo = false; + } + if (!stillLoadingVideo) + { + if ((controlComponent = player.getControlPanelComponent()) != null) { + if (visualComponent == null) //no video + vPanel.setPreferredSize(new Dimension(400,25)); + vPanel.add("South", controlComponent); + } + } + add(vPanel); + } + } + parent.invalidate(); + parent.validate(); + parent.repaint(); + isSized = true; + if (stillLoadingVideo) + player.start(); + } else if (event instanceof StartEvent) { + StartEvent se = (StartEvent)event; + Time t = se.getMediaTime(); + long longt = t.getNanoseconds(); + Float from = new Float(longt); + float f = (from.floatValue() / 1000000000); + from = new Float(f); + t = player.getStopTime(); + longt = t.getNanoseconds(); + to = new Float(longt); + f = (to.floatValue() / 1000000000); + to = new Float(f); + remplisPileStart(from, to); + if (timer != null) + { + timer.cancel(); + timer = null; + } + timer = new java.util.Timer(true); + timer.schedule(new TimerTask() { + public void run() { + //this is specifically for the MPG stop time bug + if (stopTime != null) + if (player.getMediaTime().getNanoseconds() > stopTime.getNanoseconds()) + player.stop(); + cmd_nextEvent(); + }}, 0, 15); + } else if (event instanceof StopEvent) { + pauseTime = player.getMediaTime(); + + /*messy problems require messy solutions: + if the slider is present, dragging it while playing creates + a RestartingEvent, and if I set the media time here it messes up + and barely plays at all (maybe because it cancels the previously + set media time? - I don't know). + + but it seems that if you press the play/pause button on the + control widget, then you need to set the media time upon stop + (probably because of problem noted below, namely that you get + weird results if you do player.start() without setting the media + time.*/ + + if (!(event instanceof RestartingEvent)) + player.setMediaTime(pauseTime); + + stopTime = null; + + System.out.println("received StopEvent"); + + if (timer != null) + { + timer.cancel(); + timer = null; + } + if (stillLoadingVideo) + { + System.out.println("received EndOfMediaEvent"); + stillLoadingVideo = false; + player.stop(); + if ((controlComponent = player.getControlPanelComponent()) != null) { + if (visualComponent == null) //no video + vPanel.setPreferredSize(new Dimension(400,25)); + vPanel.add("South", controlComponent); + } + parent.invalidate(); + parent.validate(); + parent.repaint(); + } + } else if ( event instanceof CachingControlEvent) { + CachingControlEvent e = (CachingControlEvent) event; + System.out.println("got CachingControlEvent: " + e); + if (!isMediaAudio) + stillLoadingVideo = true; + } else if (event instanceof ControllerErrorEvent) { + player = null; + System.err.println("*** ControllerErrorEvent *** " + ((ControllerErrorEvent)event).getMessage()); + } else if (event instanceof PrefetchCompleteEvent) { + if (panel != null) { + panel.invalidate(); + } + parent.invalidate(); + parent.validate(); + parent.repaint(); + } + } +}; \ No newline at end of file diff --git a/org/thdl/savant/TextHighlightPlayer.java b/org/thdl/savant/TextHighlightPlayer.java new file mode 100644 index 0000000..ab8e592 --- /dev/null +++ b/org/thdl/savant/TextHighlightPlayer.java @@ -0,0 +1,104 @@ +package org.thdl.savant; + +import java.awt.*; +import java.util.*; +import javax.swing.*; +import javax.swing.text.*; +import java.awt.event.MouseListener; + +public class TextHighlightPlayer extends JPanel implements AnnotationPlayer +{ + protected JTextComponent text; + protected Hashtable hashStart, hashEnd, highlights; + protected Highlighter highlighter; + protected Highlighter.HighlightPainter highlightPainter; + + public TextHighlightPlayer(TranscriptView view, Color highlightcolor) + { + text = view.getTextComponent(); + text.setEditable(false); + MouseListener[] mls = (MouseListener[])(text.getListeners(MouseListener.class)); + for (int i=0; i0 && next.charAt(0)!='-') + segBuffer.append(' '); + segBuffer.append(next); + } + doc.insertString(endPos.getOffset(), segBuffer.toString(), mas0); + } + startBuffer.append(thisStart); + startBuffer.append(','); + thisEnd = String.valueOf(endPos.getOffset()); + endBuffer.append(thisEnd); + endBuffer.append(','); + idBuffer.append("s0,"); + t1Buffer.append(current.getAttributeValue("start")); + t1Buffer.append(','); + t2Buffer.append(current.getAttributeValue("end")); + t2Buffer.append(','); + + while (iter.hasNext()) + { + current = (Element)iter.next(); + + while (current.getName().equals("spkr")) + { + doc.insertString(endPos.getOffset(), "\n\n", mas0); + doc.insertString(endPos.getOffset(), current.getAttributeValue("who"), mas); + + if (iter.hasNext()) + current = (org.jdom.Element)iter.next(); + } + + doc.insertString(endPos.getOffset(), "\n", mas0); + counter++; + thisStart = String.valueOf(endPos.getOffset()); + startBuffer.append(thisStart); + startBuffer.append(','); + if (current.getAttributeValue("gls").equals("PAUSE")) + doc.insertString(endPos.getOffset(), "......", mas0); + else { + String segString = current.getAttributeValue("seg"); + StringBuffer segBuffer = new StringBuffer(); + StringTokenizer segTokenizer = new StringTokenizer(segString); + for (int i=0; segTokenizer.hasMoreTokens(); i++) { + String next = segTokenizer.nextToken(); + if (i>0 && next.charAt(0)!='-') + segBuffer.append(' '); + segBuffer.append(next); + } + doc.insertString(endPos.getOffset(), segBuffer.toString(), mas0); + } + thisEnd = String.valueOf(endPos.getOffset()); + endBuffer.append(thisEnd); + endBuffer.append(','); + thisId = "s"+String.valueOf(counter); + idBuffer.append(thisId); + idBuffer.append(','); + t1Buffer.append(current.getAttributeValue("start")); + t1Buffer.append(','); + t2Buffer.append(current.getAttributeValue("end")); + t2Buffer.append(','); + } + + idBuffer.toString(); + t1Buffer.toString(); + t2Buffer.toString(); + startBuffer.toString(); + endBuffer.toString(); + } + catch (BadLocationException ble) + { + ble.printStackTrace(); + } + } + + public JTextComponent getTextComponent() + { + return text; + } + + public Document getDocument() + { + return xmlDoc; + } + + public String getIDs() + { + return idBuffer.toString(); + } + + public String getT1s() + { + return t1Buffer.toString(); + } + + public String getT2s() + { + return t2Buffer.toString(); + } + + public String getStartOffsets() + { + return startBuffer.toString(); + } + + public String getEndOffsets() + { + return endBuffer.toString(); + } +} \ No newline at end of file diff --git a/org/thdl/tib/input/DuffPane.java b/org/thdl/tib/input/DuffPane.java new file mode 100644 index 0000000..cebe706 --- /dev/null +++ b/org/thdl/tib/input/DuffPane.java @@ -0,0 +1,1451 @@ +/* +The contents of this file are subject to the THDL Open Community License +Version 1.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License on the THDL web site +(http://www.thdl.org/). + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +License for the specific terms governing rights and limitations under the +License. + +The Initial Developer of this software is the Tibetan and Himalayan Digital +Library (THDL). Portions created by the THDL are Copyright 2001 THDL. +All Rights Reserved. + +Contributor(s): ______________________________________. +*/ + +package org.thdl.tib.input; + +import java.util.*; +import java.awt.*; +import java.awt.datatransfer.*; +import java.awt.font.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.text.*; + +import java.lang.*; +import org.thdl.tib.text.*; + +import java.io.*; +import javax.swing.text.rtf.*; + +/** +* Enables input of Tibetan text +* using Tibetan Computer Company's free cross-platform TibetanMachineWeb fonts. +* Two modes of text entry are allowed. In Tibetan mode, keystrokes are intercepted +* and reinterpreted according to the Tibetan keyboard installed. The result, of +* course, is Tibetan text, in the TibetanMachineWeb encoding. In Roman mode, +* keystrokes are not intercepted, and the font defaults to a Roman or user-defined font. +* @author Edward Garrett, Tibetan and Himalayan Digital Library +* @version 1.0 +*/ +public class DuffPane extends JTextPane implements KeyListener, FocusListener { +/* +* A central part of the Tibetan keyboard. As keys are typed, they are +* added to charList if they constitute a valid Wylie character. charList +* is added to in this manner until the user types punctuation, a vowel, +* or some action or function key. Later, when glyphs are printed to the +* screen, the {@link #newGlyphList glyphList}) is computed on the basis +* of charList. +*/ + private java.util.LinkedList charList; +/* +* This field holds a copy of the last {@link #newGlyphList}. +* Then, when a key is pressed, {@link #charList} is updated, a new +* newGlyphList is computed, and the newGlyphList is compared against +* this field. The text on the screen is then modified to reflect +* the new newGlyphList. +*/ + private java.util.List oldGlyphList; +/* +* A central component of the Tibetan input method. While {@link #charList charList} +* keeps track of the characters that have been entered, it does not organize them +* correctly into the proper glyphs. For example, charList might have four characters +* in it, 'b', 's', 'g', and 'r', but it does not know that they should be drawn as +* two glyphs, 'b' and 's-g-r'. newGlyphList is a list of glyphs +* ({@link thdl.tibetan.text.DuffCode DuffCodes}) which is formed by +* @link #recomputeGlyphs(boolean areStacksOnRight, boolean definitelyTibetan, boolean definitelySanskrit) recomputeGlyphs}, +* which constructs an optimal arrangement of glyphs from the charList. +*/ + private java.util.List newGlyphList; +/* +* This field keeps track of what is currently being typed, to account +* for characters (such as Wylie 'tsh') which correspond to more than one +* keystroke in the keyboard. It is cleared or readjusted when it is clear +* that the user has moved on to a different character. For example, after the +* user types 'khr', holdCurrent will contain only 'r', since 'khr' is not +* a valid character. +*/ + private StringBuffer holdCurrent; +/* +* This field says whether or not the character atop {@link #charList} has +* been finalized, and therefore whether subsequent keystrokes are +* allowed to displace this character. For example, if 'k' is at the top +* of charList, and isTopHypothesis is true, then typing 'h' would replace +* 'k' with 'kh'. On the other hand, were isTopHypothesis false, then typing +* 'h' would add 'h' to the top of charList instead. +*/ + private boolean isTopHypothesis; +/* +* Is the user in the process of typing a vowel? +*/ + private boolean isTypingVowel; +/* +* Is it definitely the case that the user is typing Tibetan, rather than +* Sanskrit? +*/ + private boolean isDefinitelyTibetan; +/* +* According to the active keyboard, what value is +* {@link #isDefinitelyTibetan} assigned by default when the +* keyboard is initialized by {@link #initKeyboard() initKeyboard}? +*/ + private boolean isDefinitelyTibetan_default; +/* +* According to the active keyboard, what value should +* be assigned to {@link #isDefinitelyTibetan} if the +* user has initiated a stack by typing a stack key? +* For example, in the Wylie keyboard, there is a Sanskrit +* stacking key ('+'), but no Tibetan stacking key. +* Therefore, if the user is stacking with '+', this field +* should be false, since what the user is typing must +* be Sanskrit, not Tibetan. +*/ + private boolean isDefinitelyTibetan_withStackKey; +/* +* Is it definitely the case that the user is typing Sanskrit +* (e.g. a Sanskrit stack), rather than Tibetan? +*/ + private boolean isDefinitelySanskrit; +/* +* According to the active keyboard, what value is +* {@link #isDefinitelySanskrit} assigned by default when the +* keyboard is initialized by {@link #initKeyboard() initKeyboard}? +*/ + private boolean isDefinitelySanskrit_default; +/* +* According to the active keyboard, what value should +* be assigned to {@link #isDefinitelySanskrit} if the +* user has initiated a stack by typing a stack key? +* For example, in the Wylie keyboard, there is a Sanskrit +* stacking key ('+'), but no Tibetan stacking key. +* Therefore, if the user is stacking with '+', this field +* should be true, since what the user is typing must +* be Sanskrit, not Tibetan. +*/ + private boolean isDefinitelySanskrit_withStackKey; +/* +* Is consonant stacking allowed at the moment? In the Wylie +* keyboard, consonant stacking is usually on, since stacking +* is automatic. However, in the TCC and Sambhota keyboards, +* stacking is off by default, since you can only stack when +* you've pressed a stacking key. +*/ + private boolean isStackingOn; +/* +* According to the active keyboard, is stacking on by +* default or not, assuming no stack key has been pressed? +*/ + private boolean isStackingOn_default; +/* +* Automatic stacking in Wylie is from right to left. For +* example, if the user types 'brg', the resulting glyph +* sequence is 'b' plus 'rg', not 'br' plus 'g'. If +* stacking results from the use of a stack key, +* it is from left to right. +*/ + private boolean isStackingRightToLeft; +/* +* If the character last displayed was a vowel, +* how many glyphs is the vowel composed of? +* (Some vowels, such as Wylie 'I', consist of +* two glyphs.) +*/ + private int numberOfGlyphsForLastVowel; +/* +* used for tracking changes in Wylie to TMW conversion +*/ + private int lastStart; +/* +* is the user in Tibetan typing mode? this is true +* by default +*/ + private boolean isTibetan = true; +/* +* is the user allowed to type non-Tibetan? this is true +* by default +*/ + private boolean isRomanEnabled = true; +/* +* The document displayed by this object. +*/ + private TibetanDocument doc; +/* +* The caret of {@link #doc}, used to keep track of the +* current entry/edit/deletion position. +*/ + private Caret caret; +// private StyledEditorKit editorKit; + private StyleContext styleContext; + private Style rootStyle; + private boolean skipUpdate = false; + private boolean isCutAndPasteEnabled = true; + + private String romanFontFamily; + private int romanFontSize; + private MutableAttributeSet romanAttributeSet; + +public Clipboard rtfBoard; +public DataFlavor rtfFlavor; +public RTFEditorKit rtfEd = null; + + public DuffPane() { + setupKeyboard(); + setupEditor(); +// this(new StyledEditorKit()); + } + + public DuffPane(TibetanKeyboard keyboard) { + TibetanMachineWeb.setKeyboard(keyboard); + setupKeyboard(); + setupEditor(); +// this(new StyledEditorKit(), keyboard); + } + + public DuffPane(java.net.URL keyboardURL) { + TibetanMachineWeb.setKeyboard(keyboardURL); + setupKeyboard(); + setupEditor(); +// this(new StyledEditorKit(), keyboardURL); + } + +/* + public DuffPane() { + setupKeyboard(); + setupEditor(styledEditorKit); + } + + public DuffPane(TibetanKeyboard keyboard) { + TibetanMachineWeb.setKeyboard(keyboard); + setupKeyboard(); + setupEditor(); + } + + public DuffPane(java.net.URL keyboardURL) { + TibetanMachineWeb.setKeyboard(keyboardURL); + setupKeyboard(); + setupEditor(); + } +*/ + +/** +* This method sets up the editor, assigns fonts and point sizes, +* sets the document, the caret, and adds key and focus listeners. +* +* @param sek the StyledEditorKit for the editing window +*/ + public void setupEditor() { + rtfBoard = getToolkit().getSystemClipboard(); + rtfFlavor = new DataFlavor("text/rtf", "Rich Text Format"); + rtfEd = new RTFEditorKit(); + setEditorKit(rtfEd); + styleContext = new StyleContext(); + + doc = new TibetanDocument(styleContext); + doc.setTibetanFontSize(36); + setDocument(doc); + + Style defaultStyle = styleContext.getStyle(StyleContext.DEFAULT_STYLE); + StyleConstants.setFontFamily(defaultStyle, "TibetanMachineWeb"); + StyleConstants.setFontSize(defaultStyle, 36); + + romanFontFamily = "Serif"; + romanFontSize = 14; + setRomanAttributeSet(romanFontFamily, romanFontSize); + +// newDocument(); + caret = getCaret(); + + addKeyListener(this); + addFocusListener(this); + } + +/** +* This method sets up the Tibetan keyboard. Initially it is called +* by the constructor, but it is also called internally whenever +* the active keyboard is changed. It first sets various values with +* respect to stacking, and concerning the differences between +* Tibetan and Sanskrit; and then it initializes the input method. +*/ + public void setupKeyboard() { + if (TibetanMachineWeb.hasTibetanStackingKey()) { + if (TibetanMachineWeb.hasSanskritStackingKey()) { + isDefinitelyTibetan_default = false; + isDefinitelySanskrit_default = false; + isStackingOn_default = false; + isDefinitelyTibetan_withStackKey = false; + isDefinitelySanskrit_withStackKey = false; + } + else { + isDefinitelyTibetan_default = false; + isDefinitelySanskrit_default = true; + isStackingOn_default = true; + isDefinitelyTibetan_withStackKey = true; + isDefinitelySanskrit_withStackKey = false; + } + } + else { + if (TibetanMachineWeb.hasSanskritStackingKey()) { + isDefinitelyTibetan_default = true; + isDefinitelySanskrit_default = false; + isStackingOn_default = true; + isDefinitelyTibetan_withStackKey = false; + isDefinitelySanskrit_withStackKey = true; + } + else { //no stacking key at all + isDefinitelyTibetan_default = false; + isDefinitelySanskrit_default = false; + isStackingOn_default = true; + } + } + charList = new LinkedList(); + oldGlyphList = new ArrayList(); + newGlyphList = new ArrayList(); + initKeyboard(); + } + +/** +* Registers the Extended Wylie keyboard, and sets it as +* the active keyboard. +* Unpredictable behavior will result +* if you set the keyboard in {@link org.thdl.tib.text.TibetanMachineWeb TibetanMachineWeb} +* but don't register it here. +*/ + public void registerKeyboard() { + TibetanKeyboard tk = null; + registerKeyboard(tk); + } + +/** +* Registers a keyboard, and sets it as +* the active keyboard. * Unpredictable behavior will result +* if you set the keyboard in {@link org.thdl.tib.text.TibetanMachineWeb TibetanMachineWeb} +* but don't register it in here. +* @param keyboardURL the URL of the keyboard you want to install +*/ + public void registerKeyboard(java.net.URL keyboardURL) { + TibetanMachineWeb.setKeyboard(keyboardURL); + setupKeyboard(); + } + +/** +* Registers a keyboard, and sets it as +* the active keyboard. +* Unpredictable behavior will result +* if you set the keyboard in {@link org.thdl.tib.text.TibetanMachineWeb TibetanMachineWeb} +* but don't register it in here. +* @param keyboard the keyboard you want to install +*/ + public void registerKeyboard(TibetanKeyboard keyboard) { + TibetanMachineWeb.setKeyboard(keyboard); + setupKeyboard(); + } + +/** +* Clears the current document. +*/ + public void newDocument() { + styleContext = new StyleContext(); + + doc = new TibetanDocument(styleContext); + doc.setTibetanFontSize(36); + setDocument(doc); + + Style defaultStyle = styleContext.getStyle(StyleContext.DEFAULT_STYLE); + StyleConstants.setFontFamily(defaultStyle, "TibetanMachineWeb"); + StyleConstants.setFontSize(defaultStyle, 36); + } + +/** +* Initializes the keyboard input interpreter, setting all properties +* back to their default states. This method is called whenever an action +* or function key is pressed, and also whenever the user types punctuation +* or a vowel. It is not called when the user is typing characters that +* could be part of a stack, or require manipulation of glyphs - e.g. +* backspacing, redrawing, etc. +*/ + public void initKeyboard() { + charList.clear(); + oldGlyphList.clear(); + holdCurrent = new StringBuffer(); + isTopHypothesis = false; + isTypingVowel = false; + numberOfGlyphsForLastVowel = 0; + + //for keyboard + isStackingOn = isStackingOn_default; + isStackingRightToLeft = true; + isDefinitelyTibetan = isDefinitelyTibetan_default; + isDefinitelySanskrit = isDefinitelySanskrit_default; + } + +/** +* Enables typing of Roman (non-Tibetan) text along +* with Tibetan. +*/ + public void enableRoman() { + isRomanEnabled = true; + } + +/** +* Disables typing of Roman (non-Tibetan) text. +*/ + public void disableRoman() { + isRomanEnabled = false; + } + +/** +* Checks to see if Roman input is enabled. +* @return true if so, false if not +*/ + public boolean isRomanEnabled() { + return isRomanEnabled; + } + +/** +* Checks to see if currently in Roman input mode. +* @return true if so, false if not +*/ + public boolean isRomanMode() { + return !isTibetan; + } + +/** +* Toggles between Tibetan and Roman input modes. +* Does nothing if Roman input is disabled. +* @see #enableRoman() +* @see #disableRoman() +*/ + public void toggleLanguage() { + if (isTibetan && isRomanEnabled) + isTibetan = false; + else + isTibetan = true; + } + +/** +* Inserts Roman text into this object's document, +* at the position of the caret. +* @param attr the attributes for the text to insert +* @param s the string of text to insert +*/ + public void append(String s, MutableAttributeSet attr) { + append(caret.getDot(), s, attr); + } + +/** +* Inserts Roman text into this object's document. +* @param offset the position at which to insert text +* @param attr the attributes for the text to insert +* @param s the string of text to insert +*/ + public void append(int offset, String s, MutableAttributeSet attr) { + try { + doc.insertString(offset, s, attr); + } + catch (BadLocationException ble) { + } + } + +/** +* Changes the default font size for Tibetan text entry mode. +* +* @param size a point size +*/ + public void setTibetanFontSize(int size) { + if (size > 0) + doc.setTibetanFontSize(size); + } + +/** +* Gets the current point size for Tibetan text. +* @return the current default font size for Tibetan +* text entry mode +*/ + public int getTibetanFontSize() { + return doc.getTibetanFontSize(); + } + +/** +* Changes the default font and font size for +* non-Tibetan (Roman) text entry mode. +* +* @param font a font name +* @param size a point size +*/ + public void setRomanAttributeSet(String font, int size) { + romanAttributeSet = new SimpleAttributeSet(); + StyleConstants.setFontFamily(romanAttributeSet, font); + StyleConstants.setFontSize(romanAttributeSet, size); + } + +/** +* Gets the current point size for non-Tibetan text. +* @return the current default font size for non-Tibetan +* (Roman) text entry mode +*/ + public int getRomanFontSize() { + return romanFontSize; + } + +/** +* Gets the current font used for non-Tibetan text. +* @return the current default font for non-Tibetan +* (Roman) text entry mode +*/ + public String getRomanFontFamily() { + return romanFontFamily; + } + +/** +* Backspace and remove k elements from the current caret position. +* +* @param k the number of glyphs to remove by backspace +*/ + private void backSpace(int k) { + try { + doc.remove(caret.getDot()-k, k); + } + catch (BadLocationException ble) { + } + } + +/** +* Takes an old glyph list, which should be the currently visible set of glyphs preceding the cursor, and +* then tries to redraw the glyphs in light of the newly acquired keyboard input (which led to a revised +* 'new' glyph list). For example, the old glyph list might contain 'l' and 'n', because the user had typed +* 'ln' in Extended Wylie mode. This is what you'd see on the screen. But assume that the new glyph list +* contains the stacked glyph 'l-ng', because the user has just finished typing 'lng'. This method +* compares the glyphs, then figures out whether or not backspacing is necessary, and draws whatever characters +* need to be drawn. +* For example, suppose that oldGlyphList contains the two glyphs 'l' and 'n', and newGlyphList contains a single glyph, 'lng'. +* In this case, redrawGlyphs will be instructed to backspace over both 'l' and 'n', and then insert 'lng'. +*/ + private java.util.List redrawGlyphs(java.util.List oldGlyphList, java.util.List newGlyphList) { + if (newGlyphList.isEmpty()) + return newGlyphList; + + Iterator newIter = newGlyphList.iterator(); + DuffCode newDc = (DuffCode)newIter.next(); + + int oldGlyphCount = oldGlyphList.size(); + int newGlyphCount = newGlyphList.size(); + int beginDifference = -1; //at what point does the new glyph list begin to differ from the old one? + int k=0; + + if (oldGlyphCount!=0) { + int smallerGlyphCount; + DuffCode oldDc; + + if (oldGlyphCount < newGlyphCount) + smallerGlyphCount = oldGlyphCount; + else + smallerGlyphCount = newGlyphCount; + + Iterator oldIter = oldGlyphList.iterator(); + + for (; k newGlyphCount) + backSpace(oldGlyphCount-newGlyphCount); //deals with 'pd+m' problem + + return newGlyphList; //there is no difference between new and old glyph lists + } + } + + if (beginDifference != -1) + backSpace(oldGlyphCount - beginDifference); + + java.util.List sublist = newGlyphList.subList(k, newGlyphCount); + TibetanDocument.DuffData[] dd = TibetanDocument.convertGlyphs(sublist); + doc.insertDuff(caret.getDot(), dd); + return newGlyphList; + } + +/** +* Tries to insert the vowel v at the position of the caret. +* This method must deal with various issues, such as: can the preceding +* glyph take a vowel? If not, then what? If the preceding glyph can be +* followed by a vowel, then the method has to figure out what vowel +* glyph to affix, which may depend on the immediately preceding glyph, +* but may depend on other factors as well. For example, when affixing +* gigu to the consonantal stack "k'" (ie k plus achung), the value of +* the gigu will depend on "k", not "'". +* +* @param v the vowel (in Wylie) you want to insert +*/ + private void putVowel(String v) { + if (caret.getDot()==0) { + if (!TibetanMachineWeb.isAChenRequiredBeforeVowel()) + printAChenWithVowel(v); + + return; + } + + AttributeSet attr = doc.getCharacterElement(caret.getDot()-1).getAttributes(); + String fontName = StyleConstants.getFontFamily(attr); + int fontNum; + + if (0 != (fontNum = TibetanMachineWeb.getTMWFontNumber(fontName))) { + try { + char c2 = doc.getText(caret.getDot()-1, 1).charAt(0); + int k = (int)c2; + if (k<32 || k>126) { //if previous character is formatting or some other non-character + if (!TibetanMachineWeb.isAChenRequiredBeforeVowel()) + printAChenWithVowel(v); + + return; + } + + String wylie = TibetanMachineWeb.getWylieForGlyph(fontNum, k); + if (TibetanMachineWeb.isWyliePunc(wylie)) { + if (charList.isEmpty() && !TibetanMachineWeb.isAChenRequiredBeforeVowel()) { + printAChenWithVowel(v); + return; + } + } + + DuffCode dc_1 = null; + DuffCode dc_2 = new DuffCode(fontNum, c2); + + if (caret.getDot() > 2) { + attr = doc.getCharacterElement(caret.getDot()-2).getAttributes(); + fontName = StyleConstants.getFontFamily(attr); + if (0 != (fontNum = TibetanMachineWeb.getTMWFontNumber(fontName))) { + c2 = doc.getText(caret.getDot()-2, 1).charAt(0); + dc_1 = new DuffCode(fontNum, c2); + } + } + + java.util.List before_vowel = new ArrayList(); + if (null != dc_1) + before_vowel.add(dc_1); + + before_vowel.add(dc_2); + java.util.List after_vowel = TibetanDocument.getVowel(dc_1, dc_2, v); + redrawGlyphs(before_vowel, after_vowel); + } + catch(BadLocationException ble) { + System.out.println("no--can't insert here"); + } + } + else { //0 font means not Tibetan font, so begin new Tibetan font section + if (!TibetanMachineWeb.isAChenRequiredBeforeVowel()) + printAChenWithVowel(v); + } + } + +/** +* Prints ACHEN together with the vowel v. When using the Wylie +* keyboard, or any other keyboard in which {@link thdl.tibetan.text.TibetanMachineWeb#isAChenRequiredBeforeVowel() isAChenRequiredBeforeVowel()} +* is false, this method is called frequently. +* +* @param v the vowel (in Wylie) which you want to print with ACHEN +*/ + private void printAChenWithVowel(String v) { + DuffCode[] dc_array = (DuffCode[])TibetanMachineWeb.getTibHash().get(TibetanMachineWeb.ACHEN); + DuffCode dc = dc_array[TibetanMachineWeb.TMW]; + java.util.List achenlist = TibetanDocument.getVowel(dc,v); + TibetanDocument.DuffData[] dd = TibetanDocument.convertGlyphs(achenlist); + doc.insertDuff(caret.getDot(), dd); + } + +/** +* Puts a bindu/anusvara at the current caret position. +* In the TibetanMachineWeb font, top vowels (like gigu, +* drengbu, etc.) are merged together with bindu in a +* single glyph. This method deals with the problem of +* correctly displaying a bindu given this complication. +*/ + private void putBindu() { + special_bindu_block: { + if (caret.getDot()==0) + break special_bindu_block; + + AttributeSet attr = doc.getCharacterElement(caret.getDot()-1).getAttributes(); + String fontName = StyleConstants.getFontFamily(attr); + int fontNum; + + if (0 == (fontNum = TibetanMachineWeb.getTMWFontNumber(fontName))) + break special_bindu_block; + + try { + char c2 = doc.getText(caret.getDot()-1, 1).charAt(0); + int k = (int)c2; + if (k<32 || k>126) //if previous character is formatting or some other non-character + break special_bindu_block; + + String wylie = TibetanMachineWeb.getWylieForGlyph(fontNum, k); + if (!TibetanMachineWeb.isWylieVowel(wylie)) + break special_bindu_block; + + DuffCode dc = new DuffCode(fontNum, c2); + java.util.List beforecaret = new ArrayList(); + beforecaret.add(dc); + java.util.List bindulist = TibetanDocument.getBindu(dc); + redrawGlyphs(beforecaret, bindulist); + initKeyboard(); + return; + } + catch(BadLocationException ble) { + System.out.println("no--can't do this bindu maneuver"); + } + } + + TibetanDocument.DuffData[] dd = TibetanDocument.convertGlyphs(TibetanDocument.getBindu(null)); + doc.insertDuff(caret.getDot(), dd); + initKeyboard(); + } + +/** +* Required by implementations of the +* {@link java.awt.event.FocusListener FocusListener} interface, +* this method simply initializes the keyboard whenever this +* object gains focus. +* +* @param e a FocusEvent +*/ + public void focusGained(FocusEvent e) { + } + +/** +* Required by implementations of the +* {@link java.awt.event.FocusListener FocusListener} interface, +* this method does nothing. +* +* @param e a FocusEvent +*/ + public void focusLost(FocusEvent e) { + initKeyboard(); + } + +class RTFSelection implements ClipboardOwner, Transferable { + DataFlavor[] supportedFlavor; + ByteArrayOutputStream rtfOut; + String plainText; + + RTFSelection(StyledDocument doc, int offset, int length) { + supportedFlavor = new DataFlavor[2]; + supportedFlavor[0] = rtfFlavor; + supportedFlavor[1] = DataFlavor.stringFlavor; + try { + //construct new document that contains only portion of text you want to copy + //this workaround is due to bug 4129911, which will not be fixed (see below after source code) + StyledDocument newDoc = new DefaultStyledDocument(); + for (int i=offset; i 1 && isStackingRightToLeft) { + String s = (String)charList.removeLast(); + newGlyphList = TibetanDocument.getGlyphs(charList, isStackingRightToLeft, isDefinitelyTibetan, isDefinitelySanskrit); + oldGlyphList = redrawGlyphs(oldGlyphList, newGlyphList); + initKeyboard(); + charList.add(s); + newGlyphList = TibetanDocument.getGlyphs(charList, isStackingRightToLeft, isDefinitelyTibetan, isDefinitelySanskrit); + oldGlyphList = redrawGlyphs(oldGlyphList, newGlyphList); + holdCurrent = new StringBuffer(); + isTopHypothesis = false; + isStackingOn = true; + isStackingRightToLeft = false; + isDefinitelyTibetan = isDefinitelyTibetan_withStackKey; + isDefinitelySanskrit = isDefinitelySanskrit_withStackKey; + } + else { + holdCurrent = new StringBuffer(); + isTopHypothesis = false; + isStackingOn = true; + isStackingRightToLeft = false; + isDefinitelyTibetan = isDefinitelyTibetan_withStackKey; + isDefinitelySanskrit = isDefinitelySanskrit_withStackKey; + } + break key_block; + } + else { //stacking must be pre/post + if (!isStackingOn || (isStackingOn && isDefinitelyTibetan==isDefinitelyTibetan_default)) { + initKeyboard(); + isStackingOn = true; + isStackingRightToLeft = false; + isDefinitelyTibetan = isDefinitelyTibetan_withStackKey; + isDefinitelySanskrit = isDefinitelySanskrit_withStackKey; + } + else {try { + char ch = doc.getText(caret.getDot()-1, 1).charAt(0); + AttributeSet attr = doc.getCharacterElement(caret.getDot()-1).getAttributes(); + String fontName = StyleConstants.getFontFamily(attr); + int fontNum = TibetanMachineWeb.getTMWFontNumber(fontName); + + if (0 == fontNum) { + initKeyboard(); + isStackingOn = true; + isStackingRightToLeft = false; + isDefinitelyTibetan = isDefinitelyTibetan_withStackKey; + isDefinitelySanskrit = isDefinitelySanskrit_withStackKey; + } + else { + initKeyboard(); + DuffCode dc = new DuffCode(fontNum, ch); + + if (!TibetanMachineWeb.isStack(dc) && !TibetanMachineWeb.isSanskritStack(dc)) { + isStackingOn = true; + isStackingRightToLeft = false; + isDefinitelyTibetan = isDefinitelyTibetan_withStackKey; + isDefinitelySanskrit = isDefinitelySanskrit_withStackKey; + } + } + } + catch (BadLocationException ble) { + initKeyboard(); + }} + break key_block; + } + } + } + + switch (c) { + case KeyEvent.VK_TAB: + case KeyEvent.VK_ENTER: + case KeyEvent.VK_ESCAPE: + initKeyboard(); + break; + + case KeyEvent.VK_BACK_SPACE: + if (shouldIBackSpace) { + backSpace(1); + break; + } + + default: + String val = String.valueOf(c); + + if (TibetanMachineWeb.isPunc(val)) { //punctuation + val = TibetanMachineWeb.getWylieForPunc(val); + + if (val.charAt(0) == TibetanMachineWeb.BINDU) + putBindu(); + + else { + DuffCode puncDc = TibetanMachineWeb.getGlyph(val); + MutableAttributeSet mas = TibetanMachineWeb.getAttributeSet(puncDc.getFontNum()); + doc.appendDuff(caret.getDot(), String.valueOf(puncDc.getCharacter()), mas); + } + + initKeyboard(); + return; + } + + if (charList.size()==0) { //add current character to charList if possible + holdCurrent.append(c); + String s = holdCurrent.toString(); + + if (TibetanMachineWeb.isVowel(s)) { + s = TibetanMachineWeb.getWylieForVowel(s); + + if (isTypingVowel) //note: this takes care of multiple keystroke vowels like 'ai' + backSpace(numberOfGlyphsForLastVowel); + + putVowel(s); + isTypingVowel = true; + } + else { + if (isTypingVowel) { + isTypingVowel = false; + s = String.valueOf(c); + holdCurrent = new StringBuffer(s); + } + + if (TibetanMachineWeb.isVowel(s)) { + s = TibetanMachineWeb.getWylieForVowel(s); + putVowel(s); + isTypingVowel = true; + } + + else if (TibetanMachineWeb.isChar(s)) { + s = TibetanMachineWeb.getWylieForChar(s); + charList.add(s); + isTopHypothesis = true; + newGlyphList = TibetanDocument.getGlyphs(charList, isStackingRightToLeft, isDefinitelyTibetan, isDefinitelySanskrit); + oldGlyphList = redrawGlyphs(oldGlyphList, newGlyphList); + } + else + isTopHypothesis = false; + } + } + else { //there is already a character in charList + holdCurrent.append(c); + String s = holdCurrent.toString(); + + if (TibetanMachineWeb.isVowel(s)) { //the holding string is a vowel + s = TibetanMachineWeb.getWylieForVowel(s); + initKeyboard(); + isTypingVowel = true; + putVowel(s); + } + else if (TibetanMachineWeb.isChar(s)) { //the holding string is a character + String s2 = TibetanMachineWeb.getWylieForChar(s); + + if (isTopHypothesis) { + if (TibetanMachineWeb.isAChungConsonant() && isStackingOn && charList.size()>1 && s2.equals(TibetanMachineWeb.ACHUNG)) { + charList.removeLast(); + newGlyphList = TibetanDocument.getGlyphs(charList, isStackingRightToLeft, isDefinitelyTibetan, isDefinitelySanskrit); + oldGlyphList = redrawGlyphs(oldGlyphList, newGlyphList); + putVowel(TibetanMachineWeb.A_VOWEL); + initKeyboard(); + break key_block; + } + charList.set(charList.size()-1, s2); + newGlyphList = TibetanDocument.getGlyphs(charList, isStackingRightToLeft, isDefinitelyTibetan, isDefinitelySanskrit); + oldGlyphList = redrawGlyphs(oldGlyphList, newGlyphList); + } + else { + if (!isStackingOn) { + initKeyboard(); + holdCurrent = new StringBuffer(s); + } + else if (TibetanMachineWeb.isAChungConsonant() && s2.equals(TibetanMachineWeb.ACHUNG)) { + putVowel(TibetanMachineWeb.A_VOWEL); + initKeyboard(); + break key_block; + } + + charList.add(s2); + isTopHypothesis = true; + newGlyphList = TibetanDocument.getGlyphs(charList, isStackingRightToLeft, isDefinitelyTibetan, isDefinitelySanskrit); + oldGlyphList = redrawGlyphs(oldGlyphList, newGlyphList); + } + } + else { //the holding string is not a character + if (isTopHypothesis) { //finalize top character and add new hypothesis to top + holdCurrent = new StringBuffer(); + holdCurrent.append(c); + s = holdCurrent.toString(); + + if (TibetanMachineWeb.isVowel(s)) { + String s2 = TibetanMachineWeb.getWylieForVowel(s); + putVowel(s2); + initKeyboard(); + isTypingVowel = true; + holdCurrent = new StringBuffer(s); + } + else { + if (TibetanMachineWeb.isStackingMedial() && !isStackingRightToLeft) + initKeyboard(); + + if (TibetanMachineWeb.isChar(s)) { + String s2 = TibetanMachineWeb.getWylieForChar(s); + + if (!isStackingOn) + initKeyboard(); + + else if (TibetanMachineWeb.isAChungConsonant() && s2.equals(TibetanMachineWeb.ACHUNG)) { + putVowel(TibetanMachineWeb.A_VOWEL); + initKeyboard(); + break key_block; + } + + charList.add(s2); + newGlyphList = TibetanDocument.getGlyphs(charList, isStackingRightToLeft, isDefinitelyTibetan, isDefinitelySanskrit); + oldGlyphList = redrawGlyphs(oldGlyphList, newGlyphList); + } + else { + holdCurrent = new StringBuffer(s); + isTopHypothesis = false; + } + } + } + else { //top char is just a guess! just keep it in holdCurrent + } + } + } + } //end switch + } //end key_block + } + +/** +* Converts the entire document to Extended Wylie. +*/ + public void toWylie() { + int start = getSelectionStart(); + int end = getSelectionEnd(); + + toWylie(start, end); + } + +/** +* Converts the specified portion +* of this object's document to Extended Wylie. +* +* @param start the point from which to begin converting to Wylie +* @param end the point at which to stop converting to Wylie +*/ + public void toWylie(int start, int end) { + if (start == end) + return; + + DuffCode[] dc_array; + AttributeSet attr; + String fontName; + Position endPos; + int fontNum; + DuffCode dc; + char ch; + int i; + + java.util.List dcs = new ArrayList(); + + try { + endPos = doc.createPosition(end); + i = start; + + while (i < endPos.getOffset()+1) { + attr = doc.getCharacterElement(i).getAttributes(); + fontName = StyleConstants.getFontFamily(attr); + + if ((0 == (fontNum = TibetanMachineWeb.getTMWFontNumber(fontName))) || i==endPos.getOffset()) { + if (i != start) { + dc_array = new DuffCode[0]; + dc_array = (DuffCode[])dcs.toArray(dc_array); + doc.remove(start, i-start); + append(start, TibetanDocument.getWylie(dc_array), romanAttributeSet); + dcs.clear(); + } + start = i+1; + } + else { + ch = doc.getText(i,1).charAt(0); + dc = new DuffCode(fontNum, ch); + dcs.add(dc); + } + + i++; + } + } + catch (BadLocationException ble) { + ble.printStackTrace(); + } + } + +/** +* Converts a string of Extended Wylie to TibetanMachineWeb, and +* inserts it at the specified position. +* +* @param wylie the string of Wylie to convert +* @param offset the position at which to insert the conversion +*/ + public void toTibetanMachineWeb(String wylie, int offset) { + try { + StringTokenizer sTok = new StringTokenizer(wylie, "\n\t", true); + while (sTok.hasMoreTokens()) { + String next = sTok.nextToken(); + if (next.equals("\n") || next.equals("\t")) { + try { + doc.insertString(offset, next, null); + offset++; + } catch (BadLocationException ble) { + ble.printStackTrace(); + } + } else { + TibetanDocument.DuffData[] dd = TibetanDocument.getTibetanMachineWeb(next); + offset = doc.insertDuff(offset, dd); + } + } + } + catch (InvalidWylieException iwe) { + JOptionPane.showMessageDialog(this, + "The Wylie you are trying to convert is invalid, " + + "beginning from:\n " + iwe.getCulpritInContext() + "\n" + + "The culprit is probably the character '"+iwe.getCulprit()+"'."); + } + } + +/** +* Converts the currently selected text from Extended Wylie to TibetanMachineWeb. +*/ + public void toTibetanMachineWeb() { + int start = getSelectionStart(); + int end = getSelectionEnd(); + + toTibetanMachineWeb(start, end); + } + +/** +* Converts a stretch of text from Extended Wylie to TibetanMachineWeb. +* @param start the begin point for the conversion +* @param end the end point for the conversion +*/ + public void toTibetanMachineWeb(int start, int end) { + if (start == end) + return; + + StringBuffer sb; + AttributeSet attr; + String fontName; + int fontNum; + Position endPos; + int i; + + try { + sb = new StringBuffer(); + endPos = doc.createPosition(end); + i = start; + + while (i < endPos.getOffset()+1) { + attr = doc.getCharacterElement(i).getAttributes(); + fontName = StyleConstants.getFontFamily(attr); + + if ((0 != (fontNum = TibetanMachineWeb.getTMWFontNumber(fontName))) || i==endPos.getOffset()) { + if (i != start) { + try { + TibetanDocument.DuffData[] duffdata = TibetanDocument.getTibetanMachineWeb(sb.toString()); + doc.remove(start, i-start); + doc.insertDuff(start, duffdata); + } + catch (InvalidWylieException iwe) { + JOptionPane.showMessageDialog(this, + "The Wylie you are trying to convert is invalid, " + + "beginning from:\n " + iwe.getCulpritInContext() + + "\nThe culprit is probably the character '" + + iwe.getCulprit() + "'."); + return; + } + } + start = i+1; + } + else + sb.append(doc.getText(i, 1)); + + i++; + } + } + catch (BadLocationException ble) { + ble.printStackTrace(); + } + } + +} diff --git a/org/thdl/tib/input/Jskad.java b/org/thdl/tib/input/Jskad.java new file mode 100644 index 0000000..15d240c --- /dev/null +++ b/org/thdl/tib/input/Jskad.java @@ -0,0 +1,932 @@ +/* +The contents of this file are subject to the THDL Open Community License +Version 1.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License on the THDL web site +(http://www.thdl.org/). + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +License for the specific terms governing rights and limitations under the +License. + +The Initial Developer of this software is the Tibetan and Himalayan Digital +Library (THDL). Portions created by the THDL are Copyright 2001 THDL. +All Rights Reserved. + +Contributor(s): ______________________________________. +*/ + +package org.thdl.tib.input; + +import java.io.*; +import java.net.URL; +import java.awt.*; +import java.awt.event.*; + +import java.awt.print.*; +import javax.swing.plaf.basic.*; + +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.text.*; +import javax.swing.text.rtf.*; + +import org.thdl.tib.text.*; + +/** +* A simple Tibetan text editor. Jskad editors lack most of the +* functionality of a word-processor, but they do provide +* multiple keyboard input methods, as well as +* conversion routines to go back and forth between Extended +* Wylie and TibetanMachineWeb. +*

+* Jskad embeds {@link DuffPane DuffPane}, which is the editing +* window where the keyboard logic and most functionality is housed. +*

+* Depending on the object passed to the constructor, +* a Jskad object can be used in either an application or an +* applet. +* @author Edward Garrett, Tibetan and Himalayan Digital Library +* @version 1.0 +*/ +public class Jskad extends JPanel implements DocumentListener { + private String fontName = ""; + private JComboBox fontFamilies, fontSizes; + private JFileChooser fileChooser; + private javax.swing.filechooser.FileFilter rtfFilter; + private javax.swing.filechooser.FileFilter txtFilter; + private int fontSize = 0; + private Object parentObject = null; + private static int numberOfTibsRTFOpen = 0; + private static int x_size; + private static int y_size; + private TibetanKeyboard sambhota1Keyboard = null, duff1Keyboard = null, duff2Keyboard = null; + +/** +* The text editing window which this Jskad object embeds. +*/ + public DuffPane dp; +/** +* Has the document been saved since it was last changed? +*/ + public boolean hasChanged = false; + +/** +* The filename, if any, associated with this instance of Jskad. +*/ + public String fileName = null; + +/** +* @param parent the object that embeds this instance of Jskad. +* Supported objects include JFrames and JApplets. If the parent +* is a JApplet then the File menu is omitted from the menu bar. +*/ + public Jskad(final Object parent) { + parentObject = parent; + numberOfTibsRTFOpen++; + JMenuBar menuBar = new JMenuBar(); + + if (parentObject instanceof JFrame || parentObject instanceof JInternalFrame) { + fileChooser = new JFileChooser(); + rtfFilter = new RTFFilter(); + txtFilter = new TXTFilter(); + fileChooser.addChoosableFileFilter(rtfFilter); + + JMenu fileMenu = new JMenu("File"); + + JMenuItem newItem = new JMenuItem("New"); +// newItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,2)); //Ctrl-n + newItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + newFile(); + } + }); + fileMenu.add(newItem); + + JMenuItem openItem = new JMenuItem("Open"); +// openItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,2)); //Ctrl-o + openItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + openFile(); + } + }); + fileMenu.add(openItem); + + if (parentObject instanceof JFrame) { + JMenuItem closeItem = new JMenuItem("Close"); + closeItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (!hasChanged || hasChanged && checkSave()) { + numberOfTibsRTFOpen--; + + if (numberOfTibsRTFOpen == 0) + System.exit(0); + else { + final JFrame parentFrame = (JFrame)parentObject; + parentFrame.dispose(); + } + } + } + }); + fileMenu.add(closeItem); + } + + JMenuItem saveItem = new JMenuItem("Save"); +// saveItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,2)); //Ctrl-s + saveItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (fileName == null) + saveAsFile(); + else + saveFile(); + } + + }); + fileMenu.addSeparator(); + fileMenu.add(saveItem); + + JMenuItem saveAsItem = new JMenuItem("Save as"); + saveAsItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + saveAsFile(); + } + }); + fileMenu.add(saveAsItem); + + menuBar.add(fileMenu); + } + + JMenu editMenu = new JMenu("Edit"); + + if (parentObject instanceof JFrame || parentObject instanceof JInternalFrame) { + JMenuItem cutItem = new JMenuItem("Cut"); + cutItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X,2)); //Ctrl-x + cutItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + cutSelection(); + } + }); + editMenu.add(cutItem); + + JMenuItem copyItem = new JMenuItem("Copy"); + copyItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C,2)); //Ctrl-c + copyItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + copySelection(); + } + }); + editMenu.add(copyItem); + + JMenuItem pasteItem = new JMenuItem("Paste"); + pasteItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V,2)); //Ctrl-v + pasteItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + pasteSelection(); + } + }); + editMenu.add(pasteItem); + editMenu.addSeparator(); + + JMenuItem selectallItem = new JMenuItem("Select All"); + selectallItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A,2)); //Ctrl-a + selectallItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dp.setSelectionStart(0); + dp.setSelectionEnd(dp.getDocument().getLength()); + } + }); + editMenu.add(selectallItem); + } + + JMenuItem preferencesItem = new JMenuItem("Preferences"); + preferencesItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + getPreferences(); + } + }); + editMenu.addSeparator(); + editMenu.add(preferencesItem); + + menuBar.add(editMenu); + + JMenu toolsMenu = new JMenu("Tools"); + + JMenuItem TMWWylieItem = new JMenuItem("Convert Tibetan to Wylie"); + TMWWylieItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + toWylie(); + } + }); + toolsMenu.add(TMWWylieItem); + + JMenuItem wylieTMWItem = new JMenuItem("Convert Wylie to Tibetan"); + wylieTMWItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + toTibetan(); + } + }); + toolsMenu.add(wylieTMWItem); + + if (parentObject instanceof JFrame || parentObject instanceof JInternalFrame) { + JMenuItem importItem = new JMenuItem("Import Wylie as Tibetan"); + importItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + importWylie(); + } + }); + toolsMenu.addSeparator(); + toolsMenu.add(importItem); + } + + menuBar.add(toolsMenu); + + JMenu infoMenu = new JMenu("Info"); + + JMenuItem aboutItem = new JMenuItem("About"); + aboutItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JOptionPane.showMessageDialog(Jskad.this, + "Copyright 2001 Tibetan and Himalayan Digital Library\n"+ + "Programmed by Edward Garrett\n\n"+ + "This software is protected by the terms of the\n"+ + "THDL Open Community License, Version 1.0.\n"+ + "It uses Tibetan Computer Company (http://www.tibet.dk/tcc/)\n"+ + "fonts created by Tony Duff and made available by the\n"+ + "Trace Foundation (http://trace.org/). Jskad also includes\n"+ + "software written and copyrighted by Wildcrest Associates\n"+ + "(http://www.wildcrest.com).\n\n"+ + "For more information, or to download the source code\n"+ + "for Jskad, see our web site:\n"+ + " http://www.thdl.org/", + "About Jskad 1.0", + JOptionPane.PLAIN_MESSAGE); + } + }); + infoMenu.add(aboutItem); + + menuBar.add(infoMenu); + + JToolBar toolBar = new JToolBar(); + toolBar.setBorder(null); + toolBar.addSeparator(); + toolBar.add(new JLabel("Input mode:")); + toolBar.addSeparator(); + + String[] input_modes = {"Tibetan","Roman"}; + final JComboBox inputmethods = new JComboBox(input_modes); + inputmethods.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + switch (inputmethods.getSelectedIndex()) { + case 0: //Tibetan + if (dp.isRomanMode()) + dp.toggleLanguage(); + break; + + case 1: //Roman + if (!dp.isRomanMode() && dp.isRomanEnabled()) + dp.toggleLanguage(); + break; + } + } + }); + + + toolBar.add(inputmethods); + toolBar.add(Box.createHorizontalGlue()); + + toolBar.add(new JLabel("Keyboard:")); + toolBar.addSeparator(); + + String[] keyboard_options = {"Extended Wylie","TCC Keyboard #1","TCC Keyboard #2","Sambhota Keymap One"}; + final JComboBox keyboards = new JComboBox(keyboard_options); + keyboards.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + switch (keyboards.getSelectedIndex()) { + case 0: //Extended Wylie + installKeyboard("wylie"); + break; + + case 1: //TCC 1 + if (duff1Keyboard == null) + duff1Keyboard = installKeyboard("tcc1"); + else + dp.registerKeyboard(duff1Keyboard); + break; + + case 2: //TCC 2 + if (duff2Keyboard == null) + duff2Keyboard = installKeyboard("tcc2"); + else + dp.registerKeyboard(duff2Keyboard); + break; + + case 3: //Sambhota + if (sambhota1Keyboard == null) + sambhota1Keyboard = installKeyboard("sambhota1"); + else + dp.registerKeyboard(sambhota1Keyboard); + break; + } + } + }); + toolBar.add(keyboards); + toolBar.add(Box.createHorizontalGlue()); + + dp = new DuffPane(); + JScrollPane sp = new JScrollPane(dp, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + dp.getDocument().addDocumentListener(this); + + if (parentObject instanceof JFrame) { + final JFrame parentFrame = (JFrame)parentObject; + parentFrame.setJMenuBar(menuBar); + parentFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + parentFrame.addWindowListener(new WindowAdapter () { + public void windowClosing (WindowEvent e) { + if (!hasChanged || hasChanged && checkSave()) { + numberOfTibsRTFOpen--; + if (numberOfTibsRTFOpen == 0) + System.exit(0); + else + parentFrame.dispose(); + } + } + }); + } + + else if (parentObject instanceof JInternalFrame) { + final JInternalFrame parentFrame = (JInternalFrame)parentObject; + parentFrame.setJMenuBar(menuBar); + } + + else if (parentObject instanceof JApplet) { + JApplet parentApplet = (JApplet)parentObject; + parentApplet.setJMenuBar(menuBar); + dp.disableCutAndPaste(); + } + + setLayout(new BorderLayout()); + add("North", toolBar); + add("Center", sp); + } + + private void getPreferences() { + GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment(); + String[] fontNames = genv.getAvailableFontFamilyNames(); + + JPanel tibetanPanel; + JComboBox tibetanFontSizes; + + tibetanPanel = new JPanel(); + tibetanPanel.setBorder(BorderFactory.createTitledBorder("Set Tibetan Font Size")); + tibetanFontSizes = new JComboBox(new String[] {"8","10","12","14","16","18","20","22","24","26","28","30","32","34","36","48","72"}); + tibetanFontSizes.setMaximumSize(tibetanFontSizes.getPreferredSize()); + tibetanFontSizes.setSelectedItem(String.valueOf(dp.getTibetanFontSize())); + tibetanFontSizes.setEditable(true); + tibetanPanel.add(tibetanFontSizes); + + JPanel romanPanel; + JComboBox romanFontFamilies; + JComboBox romanFontSizes; + + romanPanel = new JPanel(); + romanPanel.setBorder(BorderFactory.createTitledBorder("Set non-Tibetan Font and Size")); + romanFontFamilies = new JComboBox(fontNames); + romanFontFamilies.setMaximumSize(romanFontFamilies.getPreferredSize()); + romanFontFamilies.setSelectedItem(dp.getRomanFontFamily()); + romanFontFamilies.setEditable(true); + romanFontSizes = new JComboBox(new String[] {"8","10","12","14","16","18","20","22","24","26","28","30","32","34","36","48","72"}); + romanFontSizes.setMaximumSize(romanFontSizes.getPreferredSize()); + romanFontSizes.setSelectedItem(String.valueOf(dp.getRomanFontSize())); + romanFontSizes.setEditable(true); + romanPanel.setLayout(new GridLayout(1,2)); + romanPanel.add(romanFontFamilies); + romanPanel.add(romanFontSizes); + + JPanel preferencesPanel = new JPanel(); + preferencesPanel.setLayout(new GridLayout(2,1)); + preferencesPanel.add(tibetanPanel); + preferencesPanel.add(romanPanel); + + JOptionPane pane = new JOptionPane(preferencesPanel); + JDialog dialog = pane.createDialog(this, "Preferences"); + dialog.show(); + + int size; + try { + size = Integer.parseInt(tibetanFontSizes.getSelectedItem().toString()); + } + catch (NumberFormatException ne) { + size = dp.getTibetanFontSize(); + } + dp.setTibetanFontSize(size); + + String font = romanFontFamilies.getSelectedItem().toString(); + try { + size = Integer.parseInt(romanFontSizes.getSelectedItem().toString()); + } + catch (NumberFormatException ne) { + size = dp.getRomanFontSize(); + } + dp.setRomanAttributeSet(font, size); + } + + private void newFile() { + if (dp.getDocument().getLength()>0 && parentObject instanceof JFrame) { + JFrame parentFrame = (JFrame)parentObject; + JFrame newFrame = new JFrame("Jskad"); + Point point = parentFrame.getLocationOnScreen(); + newFrame.setSize(parentFrame.getSize().width, parentFrame.getSize().height); + newFrame.setLocation(point.x+50, point.y+50); + newFrame.getContentPane().add(new Jskad(newFrame)); + newFrame.setVisible(true); + } + else { + if (parentObject instanceof JFrame) { + JFrame parentFrame = (JFrame)parentObject; + parentFrame.setTitle("Jskad"); + } + else if (parentObject instanceof JInternalFrame) { + JInternalFrame parentFrame = (JInternalFrame)parentObject; + parentFrame.setTitle("Jskad"); + } + dp.newDocument(); + dp.getDocument().addDocumentListener(Jskad.this); + hasChanged = false; + } + } + + private void openFile() { + fileChooser = new JFileChooser(); + fileChooser.addChoosableFileFilter(rtfFilter); + + if (dp.getDocument().getLength()>0 && parentObject instanceof JFrame) { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + if (fileChooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return; + } + + final File fileChosen = fileChooser.getSelectedFile(); + final String f_name = fileChosen.getAbsolutePath(); + + try { + JFrame parentFrame = (JFrame)parentObject; + InputStream in = new FileInputStream(fileChosen); + JFrame newFrame = new JFrame(f_name); + Point point = parentFrame.getLocationOnScreen(); + newFrame.setSize(x_size, y_size); + newFrame.setLocation(point.x+50, point.y+50); + Jskad newRTF = new Jskad(newFrame); + newFrame.getContentPane().add(newRTF); + newRTF.dp.rtfEd.read(in, newRTF.dp.getDocument(), 0); + newRTF.dp.getDocument().addDocumentListener(newRTF); + in.close(); + newFrame.setTitle(f_name); + newRTF.fileName = new String(f_name); + newRTF.hasChanged = false; + newRTF.dp.getCaret().setDot(0); + newFrame.setVisible(true); + } + catch (FileNotFoundException fnfe) { + } + catch (IOException ioe) { + } + catch (BadLocationException ble) { + } + } + else { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + if (fileChooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return; + } + + final File fileChosen = fileChooser.getSelectedFile(); + final String f_name = fileChosen.getAbsolutePath(); + + try { + InputStream in = new FileInputStream(fileChosen); + dp.newDocument(); + dp.rtfEd.read(in, dp.getDocument(), 0); + in.close(); + dp.getCaret().setDot(0); + dp.getDocument().addDocumentListener(Jskad.this); + hasChanged = false; + fileName = new String(f_name); + + if (parentObject instanceof JFrame) { + JFrame parentFrame = (JFrame)parentObject; + parentFrame.setTitle(fileName); + } + else if (parentObject instanceof JInternalFrame) { + JInternalFrame parentFrame = (JInternalFrame)parentObject; + parentFrame.setTitle(fileName); + } + } + catch (FileNotFoundException fnfe) { + } + catch (IOException ioe) { + } + catch (BadLocationException ble) { + } + } + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + private void saveFile() { + String s = getSave(fileName); + if (null != s) { + if (parentObject instanceof JFrame) { + JFrame parentFrame = (JFrame)parentObject; + parentFrame.setTitle(s); + fileName = new String(s); + } + else if (parentObject instanceof JInternalFrame) { + JInternalFrame parentFrame = (JInternalFrame)parentObject; + parentFrame.setTitle(s); + fileName = new String(s); + } + } + } + + private void saveAsFile() { + String s = getSaveAs(); + if (null != s) { + if (parentObject instanceof JFrame) { + JFrame parentFrame = (JFrame)parentObject; + parentFrame.setTitle(s); + fileName = new String(s); + } + else if (parentObject instanceof JInternalFrame) { + JInternalFrame parentFrame = (JInternalFrame)parentObject; + parentFrame.setTitle(s); + fileName = new String(s); + } + } + } + + private boolean checkSave() { + int saveFirst = JOptionPane.showConfirmDialog(this, "Do you want to save your changes?", "Please select", JOptionPane.YES_NO_CANCEL_OPTION); + + switch (saveFirst) { + case JOptionPane.NO_OPTION: //don't save but do continue + return true; + + case JOptionPane.YES_OPTION: //save and continue + if (fileName == null) { + saveAsFile(); +} + else + saveFile(); + return true; + + default: + return false; + } + } + + private String getSave(String f_name) { + File fileChosen = new File(f_name); + + try { + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(fileChosen)); + dp.rtfEd.write(out, dp.getDocument(), 0, dp.getDocument().getLength()); + out.flush(); + out.close(); + hasChanged = false; + } catch (IOException exception) { + exception.printStackTrace(); + return null; + } catch (BadLocationException ble) { + ble.printStackTrace(); + } + return f_name; + } + + private String getSaveAs() { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + if (fileName == null) + fileChooser.setSelectedFile(null); + else + fileChooser.setSelectedFile(new File(fileName)); + + if (fileChooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return null; + } + + File fileChosen = fileChooser.getSelectedFile(); + String fileName = fileChosen.getAbsolutePath(); + int i = fileName.lastIndexOf('.'); + + if (i < 0) + fileName += ".rtf"; + else + fileName = fileName.substring(0, i) + ".rtf"; + + getSave(fileName); + + fileChooser.rescanCurrentDirectory(); + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return fileName; + } + + private void cutSelection() { + dp.copy(dp.getSelectionStart(), dp.getSelectionEnd(), true); + } + + private void copySelection() { + dp.copy(dp.getSelectionStart(), dp.getSelectionEnd(), false); + } + + private void pasteSelection() { + dp.paste(dp.getCaret().getDot()); + } + + private void toTibetan() { + Jskad.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + dp.toTibetanMachineWeb(); + Jskad.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + private void toWylie() { + Jskad.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + dp.toWylie(dp.getSelectionStart(), dp.getSelectionEnd()); + Jskad.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + private void importWylie() { + fileChooser.removeChoosableFileFilter(rtfFilter); + fileChooser.addChoosableFileFilter(txtFilter); + + if (fileChooser.showDialog(Jskad.this, "Import Wylie") != JFileChooser.APPROVE_OPTION) { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + fileChooser.removeChoosableFileFilter(txtFilter); + fileChooser.addChoosableFileFilter(rtfFilter); + return; + } + + File txt_fileChosen = fileChooser.getSelectedFile(); + fileChooser.rescanCurrentDirectory(); + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + fileChooser.removeChoosableFileFilter(txtFilter); + fileChooser.addChoosableFileFilter(rtfFilter); + + if (fileChooser.showDialog(Jskad.this, "Save as Tibetan") != JFileChooser.APPROVE_OPTION) { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return; + } + + File rtf_fileChosen = fileChooser.getSelectedFile(); + fileChooser.rescanCurrentDirectory(); + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + String rtf_fileName = rtf_fileChosen.getAbsolutePath(); + int i = rtf_fileName.lastIndexOf('.'); + + if (i < 0) + rtf_fileName += ".rtf"; + else + rtf_fileName = fileName.substring(0, i) + ".rtf"; + + try { + BufferedReader in = new BufferedReader(new FileReader(txt_fileChosen)); + DuffPane dp2 = new DuffPane(); + + try { + String val = in.readLine(); + + while (val != null) { + dp2.toTibetanMachineWeb(val + "\n", dp2.getCaret().getDot()); + val = in.readLine(); + } + + TibetanDocument t_doc = (TibetanDocument)dp2.getDocument(); + t_doc.writeRTFOutputStream(new FileOutputStream(new File(rtf_fileName))); + } + catch (IOException ioe) { + System.out.println("problem reading or writing file"); + } + } + catch (FileNotFoundException fnfe) { + System.out.println("problem reading file"); + } + } + + private TibetanKeyboard installKeyboard(String name) { + TibetanKeyboard tk = null; + + if (!name.equalsIgnoreCase("wylie")) { + URL keyboard_url = null; + + if (name.equalsIgnoreCase("sambhota1")) + keyboard_url = TibetanMachineWeb.class.getResource("sambhota_keyboard_1.ini"); + + else if (name.equalsIgnoreCase("tcc1")) + keyboard_url = TibetanMachineWeb.class.getResource("tcc_keyboard_1.ini"); + + else if (name.equalsIgnoreCase("tcc2")) + keyboard_url = TibetanMachineWeb.class.getResource("tcc_keyboard_2.ini"); + + if (null != keyboard_url) { + try { + tk = new TibetanKeyboard(keyboard_url); + } + catch (TibetanKeyboard.InvalidKeyboardException ike) { + System.out.println("invalid keyboard file or file not found"); + return null; + } + } + else + return null; + } + + dp.registerKeyboard(tk); + return tk; + } + +/** +* Allows use of Jskad as dependent JFrame. +* Once you've called this method, users will +* be able to close Jskad without shutting +* down your superordinate application. +*/ + public void makeDependent() { + numberOfTibsRTFOpen++; + } + +/** +* Fills the editing pane with content. If the +* editing pane already has content, this method does nothing. +* +* @param wylie the string of wylie you want to +* put in the editing pane +*/ + public void setContent(String wylie) { + if (dp.getDocument().getLength() > 0) + return; + + dp.newDocument(); + dp.toTibetanMachineWeb(wylie, 0); + } + +/** +* Enables typing of Roman (non-Tibetan) text along +* with Tibetan. +*/ + public void enableRoman() { + dp.enableRoman(); + } + +/** +* Disables typing of Roman (non-Tibetan) text. +*/ + public void disableRoman() { + dp.disableRoman(); + } + +/* + private void showAttributes(int pos) { + dp.skipUpdate = true; + + AttributeSet attr = dp.getDocument().getCharacterElement(pos).getAttributes(); + String name = StyleConstants.getFontFamily(attr); + + if (!fontName.equals(name)) { + fontName = name; + fontFamilies.setSelectedItem(name); + } + int size = StyleConstants.getFontSize(attr); + if (fontSize != size) { + fontSize = size; + fontSizes.setSelectedItem(Integer.toString(fontSize)); + } + + dp.skipUpdate = false; + } +*/ + +/** +* Required for implementations of DocumentListener. +* Does nothing. +*/ + public void changedUpdate(DocumentEvent de) { + } + +/** +* Required for implementations of DocumentListener. +* Informs the object that a change in the document +* has occurred. +* +* @param de a DocumentEvent +*/ + public void insertUpdate(DocumentEvent de) { + hasChanged = true; + } + +/** +* Required for implementations of DocumentListener. +* Informs the object that a change in the document +* has occurred. +* +* @param de a DocumentEvent +*/ + public void removeUpdate(DocumentEvent de) { + hasChanged = true; + } + + private class RTFFilter extends javax.swing.filechooser.FileFilter { + // Accept all directories and all RTF files. + + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + + String fName = f.getName(); + int i = fName.lastIndexOf('.'); + + if (i < 0) + return false; + + else { + String ext = fName.substring(i+1).toLowerCase(); + + if (ext.equals("rtf")) + return true; + else + return false; + } + } + + //the description of this filter + public String getDescription() { + return "Rich Text Format (.rtf)"; + } + } + + private class TXTFilter extends javax.swing.filechooser.FileFilter { + // Accept all directories and all TXT files. + + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + + String fName = f.getName(); + int i = fName.lastIndexOf('.'); + + if (i < 0) + return false; + + else { + String ext = fName.substring(i+1).toLowerCase(); + + if (ext.equals("txt")) + return true; + else + return false; + } + } + + //the description of this filter + public String getDescription() { + return "Text file (.txt)"; + } + } + +/** +* Runs Jskad. +* System output, including errors, is redirected to jskad.log. +* If you discover a bug, please send us an email, making sure +* to include the jskad.log file as an attachment. +*/ + public static void main(String[] args) { + + try { + PrintStream ps = new PrintStream(new FileOutputStream("jskad.log")); + System.setErr(ps); + System.setOut(ps); + } + catch (Exception e) { + } + + + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } + catch (Exception e) { + } + + JFrame f = new JFrame("Jskad"); + Dimension d = f.getToolkit().getScreenSize(); + x_size = d.width/4*3; + y_size = d.height/4*3; + f.setSize(x_size, y_size); + f.setLocation(d.width/8, d.height/8); + f.getContentPane().add(new Jskad(f)); + f.setVisible(true); + } +} \ No newline at end of file diff --git a/org/thdl/tib/input/package.html b/org/thdl/tib/input/package.html new file mode 100644 index 0000000..7029c52 --- /dev/null +++ b/org/thdl/tib/input/package.html @@ -0,0 +1,35 @@ + + + + + + +Provides classes and methods for inputting Tibetan text. +

+Designed for use with the Tibetan Computer +Company's free cross-platform TibetanMachineWeb fonts, this package +contains methods for inputting Tibetan using various keyboard +input methods, including true Wylie-based input, as well as +user-defined keyboards. +

+The package includes a simple Tibetan text editor, Jskad, +which can be run as an local application or embedded in a +web page. Jskad supports a wide range of functions, including +conversion back and forth between TibetanMachineWeb and +Extended Wylie. +

+

Related Documentation

+@see org.thdl.tib.text + + diff --git a/org/thdl/tib/text/DuffCellRenderer.java b/org/thdl/tib/text/DuffCellRenderer.java new file mode 100644 index 0000000..7955a38 --- /dev/null +++ b/org/thdl/tib/text/DuffCellRenderer.java @@ -0,0 +1,140 @@ +/* +The contents of this file are subject to the AMP Open Community License +Version 1.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License on the AMP web site +(http://www.tibet.iteso.mx/Guatemala/). + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +License for the specific terms governing rights and limitations under the +License. + +The Initial Developer of this software is Andres Montano Pellegrini. Portions +created by Andres Montano Pellegrini are Copyright 2001 Andres Montano +Pellegrini. All Rights Reserved. + +Contributor(s): Edward Garrett. +*/ + +package org.thdl.tib.text; + +import java.awt.*; +import javax.swing.*; +import javax.swing.table.TableCellRenderer; +import javax.swing.border.*; +import javax.swing.text.*; +import org.thdl.tib.input.DuffPane; +import org.thdl.tib.text.*; +import org.thdl.tib.text.TibetanDocument.DuffData; +import java.io.Serializable; + +/** Used by DictionaryTable to display a Tibetan word or phrase + (in either Roman or Tibetan script) in a single cell. + + @author Andrés Montano Pellegrini + @see DictionaryTable +*/ +public class DuffCellRenderer extends DuffPane implements TableCellRenderer, Serializable +{ + + protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1); + + // We need a place to store the color the DuffPane should be returned + // to after its foreground and background colors have been set + // to the selection background color. + // These ivars will be made protected when their names are finalized. + private Color unselectedForeground; + private Color unselectedBackground; + + public DuffCellRenderer() + { + super(); + setOpaque(true); + setBorder(noFocusBorder); + } + + /** + * Overrides JComponent.setForeground to assign + * the unselected-foreground color to the specified color. + * + * @param c set the foreground color to this value + */ + public void setForeground(Color c) { + super.setForeground(c); + unselectedForeground = c; + } + + /** + * Overrides JComponent.setForeground to assign + * the unselected-background color to the specified color. + * + * @param c set the background color to this value + */ + public void setBackground(Color c) { + super.setBackground(c); + unselectedBackground = c; + } + + /** + * Notification from the UIManager that the look and feel + * [L&F] has changed. + * Replaces the current UI object with the latest version from the + * UIManager. + * + * @see JComponent#updateUI + */ + public void updateUI() { + super.updateUI(); + setForeground(null); + setBackground(null); + } + + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, int row, int column) + { + if (isSelected) + { + super.setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + } + else + { + super.setForeground((unselectedForeground != null) ? unselectedForeground : table.getForeground()); + super.setBackground((unselectedBackground != null) ? unselectedBackground : table.getBackground()); + } + + if (hasFocus) { + setBorder( UIManager.getBorder("Table.focusCellHighlightBorder") ); + if (table.isCellEditable(row, column)) { + super.setForeground( UIManager.getColor("Table.focusCellForeground") ); + super.setBackground( UIManager.getColor("Table.focusCellBackground") ); + } + } else { + setBorder(noFocusBorder); + } + + setValue(value); + + // ---- begin optimization to avoid painting background ---- + Color back = getBackground(); + boolean colorMatch = (back != null) && ( back.equals(table.getBackground()) ) && table.isOpaque(); + setOpaque(!colorMatch); + // ---- end optimization to aviod painting background ---- + + return this; + } + + public void setValue(Object value) + { + TibetanDocument doc = (TibetanDocument) getDocument(); + try + { + doc.remove(0, doc.getLength()); + } + catch (Exception e) + { + System.out.println(e); + } + doc.insertDuff(0, (DuffData []) value); + } +} \ No newline at end of file diff --git a/org/thdl/tib/text/DuffCode.java b/org/thdl/tib/text/DuffCode.java new file mode 100644 index 0000000..0d53222 --- /dev/null +++ b/org/thdl/tib/text/DuffCode.java @@ -0,0 +1,165 @@ +/* +The contents of this file are subject to the THDL Open Community License +Version 1.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License on the THDL web site +(http://www.thdl.org/). + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +License for the specific terms governing rights and limitations under the +License. + +The Initial Developer of this software is the Tibetan and Himalayan Digital +Library (THDL). Portions created by the THDL are Copyright 2001 THDL. +All Rights Reserved. + +Contributor(s): ______________________________________. +*/ + +package org.thdl.tib.text; + +import java.util.StringTokenizer; + +/** +* A wrapper for the primitive data types +* that combine to represent a Tibetan glyph in the +* TibetanMachineWeb family of fonts. +* +* A DuffCode consists of a font number, a character, and +* a character number. A font identification and a character +* (or character number) are sufficient to uniquely identify +* any TibetanMachineWeb glyph. +* @author Edward Garrett, Tibetan and Himalayan Digital Library +* @version 1.0 +*/ + +public class DuffCode { +/** +* the font number in which this glyph can be found, +* from 1 (TibetanMachineWeb) to 10 (TibetanMachineWeb9). +*/ + public int fontNum; +/** +* the character value of this glyph +*/ + public char character; +/** +* the character value of this glyph, as an integer +*/ + public int charNum; + +/** +* Called by {@link TibetanMachineWeb} to generate +* DuffCodes from the 'tibwn.ini' initialization file. +* This constructor expects to receive a string such as "1,33" or "33,1", +* i.e. a sequence of two numbers separated by a comma. These numbers +* represent a character: one number is its identifying font number, +* and the other is the ASCII code of the character. +* +* @param s the string to parse +* @param leftToRight should be true if the first number is the font number, +* false if the second number is the font number +*/ + public DuffCode(String s, boolean leftToRight) { + StringTokenizer st = new StringTokenizer(s,","); + + try { + String val1 = st.nextToken(); + String val2 = st.nextToken(); + + Integer num1 = new Integer(val1); + Integer num2 = new Integer(val2); + + if (leftToRight) { + fontNum = num1.intValue(); + charNum = num2.intValue(); + character = (char)charNum; + } + else { + fontNum = num2.intValue(); + charNum = num1.intValue(); + character = (char)charNum; + } + } + catch (NumberFormatException e) { + } + } + +/** +* Called to create DuffCodes on the fly +* from an identifying font number and an ASCII character. +* +* @param font the identifying number of the font +* @param ch a character +*/ + public DuffCode(int font, char ch) { + fontNum = font; + character = ch; + charNum = (int)ch; + } + +/** +* Gets the font number of this glyph. +* @return the identifying font number for this DuffCode +*/ + public int getFontNum() { + return fontNum; + } + +/** +* Gets the character for this glyph, as an integer. +* @return the identifying character, converted to an +* integer, for this DuffCode +*/ + public int getCharNum() { + return charNum; + } + +/** +* Gets the character for this glyph. +* @return the identifying character for this DuffCode +*/ + public char getCharacter() { + return character; + } + +/** +* Assigns a hashcode based on the font number and character for this glyph. +* The hashcode for a DuffCode with font=1 and character='c' +* is defined as the hash code of the string '1-c'. +* +* @return the hash code for this object +*/ + public int hashCode() { + StringBuffer sb = new StringBuffer(); + sb.append(new Integer(fontNum).toString()); + sb.append('-'); + sb.append(character); + String s = sb.toString(); + return s.hashCode(); + } + +/** +* Evaluates two DuffCodes as equal iff their +* font numbers and characters are identical. +* +* @param o the object (DuffCode) you want to compare +* @return true if this object is equal to o, false if not +*/ + public boolean equals(Object o) { + if (o instanceof DuffCode) { + DuffCode dc = (DuffCode)o; + + if (fontNum == dc.fontNum && charNum == dc.charNum) + return true; + } + return false; + } + +/** +* @return a string representation of this object +*/ + public String toString() { + return "fontNum="+fontNum+";charNum="+charNum+";character="+new Character(character).toString(); + } +} \ No newline at end of file diff --git a/org/thdl/tib/text/InvalidWylieException.java b/org/thdl/tib/text/InvalidWylieException.java new file mode 100644 index 0000000..80be2fa --- /dev/null +++ b/org/thdl/tib/text/InvalidWylieException.java @@ -0,0 +1,62 @@ +/* +The contents of this file are subject to the THDL Open Community License +Version 1.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License on the THDL web site +(http://www.thdl.org/). + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +License for the specific terms governing rights and limitations under the +License. + +The Initial Developer of this software is the Tibetan and Himalayan Digital +Library (THDL). Portions created by the THDL are Copyright 2001 THDL. +All Rights Reserved. + +Contributor(s): ______________________________________. +*/ + +package org.thdl.tib.text; + +/** +* Thrown whenever +* a process runs into invalid Extended Wylie. This is usually thrown +* while converting Wylie to +* TibetanMachineWeb when the converter runs into invalid Wylie. +* @author Edward Garrett, Tibetan and Himalayan Digital Library +* @version 1.0 +*/ +public class InvalidWylieException extends Exception { + private String wylie; + private int offset; + +/** +* Creates an InvalidWylieException. +* @param s a string of Wylie text +* @param i the offset where problems for the process began +*/ + public InvalidWylieException(String s, int i) { + wylie = s; + offset = i; + } + +/** +* Gets the character that caused the problem. +* @return the character believed to be the problem for the process +*/ + public char getCulprit() { + return wylie.charAt(offset); + } + +/** +* Gets the character that caused the problem, in context. +* @return the string of text including and following the character +* believed to be the problem for the process +*/ + public String getCulpritInContext() { + if (wylie.length() < offset+5) + return wylie.substring(offset, wylie.length()); + else + return wylie.substring(offset, offset+5); + } +} diff --git a/org/thdl/tib/text/TibetanDocument.java b/org/thdl/tib/text/TibetanDocument.java new file mode 100644 index 0000000..dbb189f --- /dev/null +++ b/org/thdl/tib/text/TibetanDocument.java @@ -0,0 +1,1292 @@ +/* +The contents of this file are subject to the THDL Open Community License +Version 1.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License on the THDL web site +(http://www.thdl.org/). + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +License for the specific terms governing rights and limitations under the +License. + +The Initial Developer of this software is the Tibetan and Himalayan Digital +Library (THDL). Portions created by the THDL are Copyright 2001 THDL. +All Rights Reserved. + +Contributor(s): ______________________________________. +*/ + +package org.thdl.tib.text; + +import java.util.*; +import javax.swing.*; +import javax.swing.text.*; +import javax.swing.text.rtf.RTFEditorKit; +import java.io.*; + +/** +* Provides methods for converting back and forth between +* Extended Wylie and TibetanMachineWeb, and for inserting Tibetan +* into a styled document. +*

+* The class provides a variety of static methods for +* converting back and forth between Extended Wylie and TibetanMachineWeb. +* The Wylie can be accessed as a String, while the TibetanMachineWeb +* can be exported as Rich Text Format. +*

+* When instantiated as an instance object, +* this class also provides methods for inserting Tibetan +* (as TibetanMachineWeb) into a styled document. +* @author Edward Garrett, Tibetan and Himalayan Digital Library +* @version 1.0 +*/ +public class TibetanDocument extends DefaultStyledDocument { + private static List chars = new ArrayList(); + private int tibetanFontSize = 36; + +/** +* Creates a TibetanDocument. +* @param styles a StyleContext, which is simply passed on +* to DefaultStyledDocument's constructor +*/ + public TibetanDocument(StyleContext styles) { + super(styles); + } + +/** +* Sets the point size used by default for Tibetan text. +* @param size the point size for Tibetan text +*/ + public void setTibetanFontSize(int size) { + tibetanFontSize = size; + } + +/** +* Gets the point size for Tibetan text. +* @return the point size used for Tibetan text +*/ + public int getTibetanFontSize() { + return tibetanFontSize; + } + +/** +* Writes the document to an OutputStream as Rich Text Format (.rtf). +* @param out the OutputStream to write to +*/ + public void writeRTFOutputStream(OutputStream out) throws IOException { + RTFEditorKit rtf = new RTFEditorKit(); + + try { + rtf.write(out, this, 0, getLength()); + } + catch (BadLocationException ble) { + } + } + +/** +* Inserts Tibetan text into the document. The font size is applied automatically, +* according to the current Tibetan font size. +* @param offset the position at which you want to insert text +* @param s the string you want to insert +* @param attr the attributes to apply, normally a particular TibetanMachineWeb font +* @see #setTibetanFontSize(int size) +*/ + public void appendDuff(int offset, String s, MutableAttributeSet attr) { + try { + StyleConstants.setFontSize(attr, tibetanFontSize); + insertString(offset, s, attr); + } + catch (BadLocationException ble) { + } + } + +/** +* Inserts a stretch of TibetanMachineWeb data into the document. +* @param glyphs the array of Tibetan data you want to insert +* @param pos the position at which you want to insert text +*/ + public int insertDuff(int pos, DuffData[] glyphs) { + if (glyphs == null) + return pos; + + MutableAttributeSet mas; + for (int i=0; i +* 1 - TibetanMachineWeb
+* 2 - TibetanMachineWeb1
+* ...
+* 10 - TibetanMachineWeb9
+*

+* The string represents a contiguous stretch of data in that +* font, i.e. a stretch of TibetanMachineWeb that doesn't require a font change. +*/ + public static class DuffData { +/** +* a string of text +*/ + public String text; +/** +* the font number for this text +*/ + public int font; + +/** +* @param s a string of TibetanMachineWeb text +* @param i a TibetanMachineWeb font number +*/ + public DuffData(String s, int i) { + text = s; + font = i; + } + } + +/** +* Converts a list of glyphs into an array of {@link TibetanDocument.DuffData DuffData}. +* The motivation for this is that most processes - for example using +* TibetanMachineWeb in HTML - only need to know what +* text to output, and when to change fonts. In general, they don't +* need to have an explicit indication for each glyph of the font +* for that glyph. +* @param glyphs the list of TibetanMachineWeb glyphs +* you want to convert +* @return an array of DuffData corresponding to this +* list of glyphs +*/ + public static DuffData[] convertGlyphs(List glyphs) { + if (glyphs.size() == 0) + return null; + List data = new ArrayList(); + StringBuffer sb = new StringBuffer(); + Iterator iter = glyphs.iterator(); + DuffCode dc = (DuffCode)iter.next(); + int lastfont = dc.fontNum; + sb.append(dc.character); + + while (iter.hasNext()) { + dc = (DuffCode)iter.next(); + if (dc.fontNum == lastfont) + sb.append(dc.character); + else { + data.add(new DuffData(sb.toString(), lastfont)); + lastfont = dc.fontNum; + sb = new StringBuffer(); + sb.append(dc.character); + } + } + + data.add(new DuffData(sb.toString(), lastfont)); + + DuffData[] dd = new DuffData[0]; + dd = (DuffData[])data.toArray(dd); + return dd; + } + +/** +* Figures out how to arrange a list of characters into glyphs. For example, if the user types 'bsgr' +* using the Extended Wylie keyboard, this method figures out that this should be represented +* as a 'b' glyph followed by a 's-g-r' glyph. If you know that the characters do not +* contain Sanskrit stacks, or do not contain Tibetan stacks, then you can specify this +* to speed the process up. Otherwise, the method will first check to see if the characters +* correspond to any Tibetan stacks, and if not, then it will check for Sanskrit stacks. +* @param chars the list of Tibetan characters you want to find glyphs for +* @param areStacksOnRight whether stacking should try to maximize from right to left (true) +* or from left to right (false). In the Extended Wylie keyboard, you try to stack from +* right to left. Thus, the character sequence r-g-r would be stacked as r followed by gr, +* rather than rg followed by r. In the Sambhota and TCC keyboards, the stack direction +* is reversed. +* @param definitelyTibetan should be true if the characters are known to be Tibetan and +* not Sanskrit +* @param definitelySanskrit should be true if the characters are known to be Sanskrit and +* not Tibetan +*/ + public static List getGlyphs(List chars, boolean areStacksOnRight, boolean definitelyTibetan, boolean definitelySanskrit) { + StringBuffer tibBuffer, sanBuffer; + String tibCluster, sanCluster; + + boolean checkTibetan, checkSanskrit; + + if (!(definitelyTibetan || definitelySanskrit)) { + checkTibetan = true; + checkSanskrit = true; + } + else { + checkTibetan = definitelyTibetan; + checkSanskrit = definitelySanskrit; + } + + int length = chars.size(); + + List glyphs = new ArrayList(); + glyphs.clear(); + + if (areStacksOnRight) { + for (int i=0; i-1; i--) { + tibBuffer = new StringBuffer(); + tibCluster = null; + + sanBuffer = new StringBuffer(); + sanCluster = null; + + Iterator iter = chars.iterator(); + + for (int k=0; k 1) { + dc = (DuffCode)glyphs.get(glyphs.size()-1); + if (!TibetanMachineWeb.isWyliePunc(TibetanMachineWeb.getWylieForGlyph(dc))) { + DuffCode dc_2 = (DuffCode)glyphs.removeLast(); + DuffCode dc_1 = (DuffCode)glyphs.removeLast(); + glyphs.addAll(getVowel(dc_1, dc_2, next)); + break vowel_block; + } + } + DuffCode[] dc_array = (DuffCode[])TibetanMachineWeb.getTibHash().get(TibetanMachineWeb.ACHEN); + dc = dc_array[TibetanMachineWeb.TMW]; + glyphs.addAll(getVowel(dc, next)); + } + + chars.clear(); + } + + isSanskrit = false; + } + + else if (TibetanMachineWeb.isWylieChar(next)) { + if (!isSanskrit) //add char to list - it is not sanskrit + chars.add(next); + + else if (wasLastSanskritStackingKey) { //add char to list - it is still part of sanskrit stack + chars.add(next); + wasLastSanskritStackingKey = false; + } + + else { //char is no longer part of sanskrit stack, therefore compute and add previous stack + glyphs.addAll(getGlyphs(chars, true, !isSanskrit, isSanskrit)); + chars.clear(); + chars.add(next); + isSanskrit = false; + wasLastSanskritStackingKey = false; + } + } + + else if (next.equals(String.valueOf(TibetanMachineWeb.WYLIE_DISAMBIGUATING_KEY))) { + if (!chars.isEmpty()) + glyphs.addAll(getGlyphs(chars, true, !isSanskrit, isSanskrit)); + + chars.clear(); + isSanskrit = false; + } + + else if (next.equals(String.valueOf(TibetanMachineWeb.WYLIE_SANSKRIT_STACKING_KEY))) { + if (!isSanskrit) { //begin sanskrit stack + switch (chars.size()) { + case 0: + break; //'+' is not "pre-stacking" key + + case 1: + isSanskrit = true; + wasLastSanskritStackingKey = true; + break; + + default: + String top_char = (String)chars.get(chars.size()-1); + chars.remove(chars.size()-1); + glyphs.addAll(getGlyphs(chars, true, !isSanskrit, isSanskrit)); + chars.clear(); + chars.add(top_char); + isSanskrit = true; + wasLastSanskritStackingKey = true; + break; + } + } + } + + else if (TibetanMachineWeb.isFormatting(next.charAt(0))) { + if (!chars.isEmpty()) + glyphs.addAll(getGlyphs(chars, true, !isSanskrit, isSanskrit)); + + dc = new DuffCode(1,next.charAt(0)); + glyphs.add(dc); + chars.clear(); + isSanskrit = false; + } + + if (next != null) + start += next.length(); + } + + if (!chars.isEmpty()) { + glyphs.addAll(getGlyphs(chars, true, !isSanskrit, isSanskrit)); + chars.clear(); + } + + DuffData[] dd = convertGlyphs(glyphs); + return dd; + } + +/** +* Gets the bindu sequence for a given context. +* In the TibetanMachineWeb fonts, bindu (anusvara) is realized +* differently depending on which vowel it attaches to. Although +* the default bindu glyph is affixed to consonants and subscript vowels, +* for superscript vowels (i, e, o, etc), there is a single glyph +* which merges the bindu and that vowel together. When you pass this +* method a glyph context, it will return a List of glyphs which +* will either consist of the original glyph followed by the default +* bindu glyph, or a composite vowel+bindu glyph. +* Note that there is only one glyph in the context. This means that +* bindus will not affix properly if superscript vowels are allowed to directly +* precede subscript vowels (e.g. pou). +* @param dc the DuffCode of the glyph you +* want to attach a bindu to +* @return a List of DuffCode glyphs that include the +* original dc, as well as a bindu +*/ + public static List getBindu(DuffCode dc) { + List bindus = new ArrayList(); + + if (null == dc) { + bindus.add(TibetanMachineWeb.getGlyph(String.valueOf(TibetanMachineWeb.BINDU))); + return bindus; + } + + if (!TibetanMachineWeb.getBinduMap().containsKey(dc)) { + bindus.add(dc); + bindus.add(TibetanMachineWeb.getGlyph(String.valueOf(TibetanMachineWeb.BINDU))); + return bindus; + } + + bindus.add((DuffCode)TibetanMachineWeb.getBinduMap().get(dc)); + return bindus; + } + +/** +* Gets the vowel sequence for a given vowel in a given context. +* Given a context, this method affixes a vowel and returns the +* context plus the vowel. Generally, it is enough to provide just +* one glyph for context. +* @param context the glyph preceding the vowel you want to affix +* @param vowel the vowel you want to affix, in Wylie +* @return a List of glyphs equal to the vowel in context +*/ + public static List getVowel(DuffCode context, String vowel) { + return getVowel(null, context, vowel); + } + +/** +* Gets the vowel sequence for a given vowel in a given context. +* Given a context, this method affixes a vowel and returns the context plus the vowel. +* Since the choice of vowel glyph depends on the consonant to which it is attached, +* generally it is enough to provide just the immediately preceding context. However, +* in some cases, double vowels are allowed - for example 'buo'. To find the correct +* glyph for 'o', we need 'b' in this case, not 'u'. Note also that some Extended +* Wylie vowels correspond to multiple glyphs in TibetanMachineWeb. For example, +* the vowel I consists of both an achung and a reverse gigu. All required glyphs +* are part of the returned List. +* @param context_1 the glyph occurring two glyphs before the vowel you want to affix +* @param context_2 the glyph immediately before the vowel you want to affix +* @param vowel the vowel you want to affix, in Wylie +* @return a List of glyphs equal to the vowel in context +*/ + + public static List getVowel(DuffCode context_1, DuffCode context_2, String vowel) { + List vowels = new ArrayList(); + +//this vowel doesn't correspond to a glyph - +//so you just return the original context + + if ( vowel.equals(TibetanMachineWeb.WYLIE_aVOWEL) || + TibetanMachineWeb.isTopVowel(context_2)) { + if (context_1 != null) + vowels.add(context_1); + + vowels.add(context_2); + return vowels; + } + +//first, the three easiest cases: ai, au, and = end) + return ""; + + java.util.List dcs = new ArrayList(); + int i = begin; + StringBuffer wylieBuffer = new StringBuffer(); + + try { + while (i < end) { + attr = getCharacterElement(i).getAttributes(); + fontName = StyleConstants.getFontFamily(attr); + + ch = getText(i,1).charAt(0); + + //current character is formatting + if (ch == '\n' || ch == '\t') { + if (dcs.size() > 0) { + DuffCode[] dc_array = new DuffCode[0]; + dc_array = (DuffCode[])dcs.toArray(dc_array); + wylieBuffer.append(TibetanDocument.getWylie(dc_array)); + dcs.clear(); + } + wylieBuffer.append(ch); + } + + //current character isn't TMW + else if ((0 == (fontNum = TibetanMachineWeb.getTMWFontNumber(fontName)))) { + if (dcs.size() > 0) { + DuffCode[] dc_array = new DuffCode[0]; + dc_array = (DuffCode[])dcs.toArray(dc_array); + wylieBuffer.append(TibetanDocument.getWylie(dc_array)); + dcs.clear(); + } + } + + //current character is convertable + else { + dc = new DuffCode(fontNum, ch); + dcs.add(dc); + } + i++; + } + if (dcs.size() > 0) { + DuffCode[] dc_array = new DuffCode[0]; + dc_array = (DuffCode[])dcs.toArray(dc_array); + wylieBuffer.append(TibetanDocument.getWylie(dc_array)); + } + return wylieBuffer.toString(); + } + catch (BadLocationException ble) { + ble.printStackTrace(); + } + + return ""; + } + +/** +* Gets the Extended Wylie for a set of glyphs. +* @param dcs an array of glyphs +* @return the Extended Wylie corresponding to these glyphs +*/ + public static String getWylie(DuffCode[] dcs) { + if (dcs.length == 0) + return null; + + AttributeSet attr; + String fontName; + int fontNum; + char ch; + String wylie; + + List glyphList = new ArrayList(); + boolean needsVowel = true; + boolean isLastVowel = false; + int start = 0; + StringBuffer wylieBuffer = new StringBuffer(); + + for (int i=start; i 0 || !glyphList.isEmpty()) { + if (needsVowel) + wylieBuffer.append(withA(glyphList)); + else + wylieBuffer.append(withoutA(glyphList)); + + glyphList.clear(); + needsVowel = true; + isLastVowel = false; + } + + wylieBuffer.append(ch); + } + else { + wylie = TibetanMachineWeb.getWylieForGlyph(dcs[i]); + + boolean containsBindu = false; + if (wylie.length() > 1 && wylie.charAt(wylie.length()-1) == TibetanMachineWeb.BINDU) { + char[] cArray = wylie.toCharArray(); + wylie = new String(cArray, 0, wylie.length()-1); + containsBindu = true; + } + + process_block: { + if (TibetanMachineWeb.isWyliePunc(wylie)) { + isLastVowel = false; + + if (glyphList.isEmpty()) + wylieBuffer.append(wylie); + + else { + if (needsVowel) + wylieBuffer.append(withA(glyphList)); + else + wylieBuffer.append(withoutA(glyphList)); + + wylieBuffer.append(wylie); //append the punctuation + + glyphList.clear(); + } + needsVowel = true; //next consonants are syllable onset, so we are awaiting vowel + } + + //isChar must come before isVowel because ACHEN has priority over WYLIE_aVOWEL + else if (TibetanMachineWeb.isWylieChar(wylie)) { + isLastVowel = false; + glyphList.add(dcs[i]); + } + + else if (TibetanMachineWeb.isWylieVowel(wylie)) { + if (isLastVowel) { + int len = wylieBuffer.length(); + int A_len = TibetanMachineWeb.A_VOWEL.length(); + + if (wylieBuffer.substring(len-A_len).equals(TibetanMachineWeb.A_VOWEL)) { + try { + if (wylie.equals(TibetanMachineWeb.i_VOWEL)) { + wylieBuffer.delete(len-A_len, len); + wylieBuffer.append(TibetanMachineWeb.I_VOWEL); + isLastVowel = false; + break process_block; + } + else if (wylie.equals(TibetanMachineWeb.reverse_i_VOWEL)) { + wylieBuffer.delete(len-A_len, len); + wylieBuffer.append(TibetanMachineWeb.reverse_I_VOWEL); + isLastVowel = false; + break process_block; + } + } + catch (StringIndexOutOfBoundsException se) { + } + + wylieBuffer.append(wylie); //append current vowel + isLastVowel = false; + } + else + wylieBuffer.append(wylie); //append current vowel + } + else { + int glyphCount = glyphList.size(); + boolean insertDisAmbig = false; + + if (0 != glyphCount) { + DuffCode top_dc = (DuffCode)glyphList.get(glyphCount-1); + String top_wylie = TibetanMachineWeb.getWylieForGlyph(top_dc); + + if (top_wylie.equals(TibetanMachineWeb.ACHEN)) { + glyphList.remove(glyphCount-1); + + if (glyphCount-1 == 0) + top_dc = null; + else { + insertDisAmbig = true; + top_dc = (DuffCode)glyphList.get(glyphCount-2); + } + } + + if (top_dc == null || !TibetanMachineWeb.getWylieForGlyph(top_dc).equals(TibetanMachineWeb.ACHUNG)) + wylieBuffer.append(withoutA(glyphList)); //append consonants in glyphList + else { + glyphCount = glyphList.size(); + glyphList.remove(glyphCount-1); + + if (glyphCount-1 != 0) + wylieBuffer.append(withA(glyphList)); + + wylieBuffer.append(TibetanMachineWeb.ACHUNG); + } + } + + if (insertDisAmbig) + wylieBuffer.append(TibetanMachineWeb.WYLIE_DISAMBIGUATING_KEY); + + wylieBuffer.append(wylie); //append vowel + + glyphList.clear(); + isLastVowel = true; + needsVowel = false; + } + } + else { //must be a stack + isLastVowel = false; + glyphList.add(dcs[i]); + } + } + + if (containsBindu) { + isLastVowel = false; + wylieBuffer.append(withoutA(glyphList)); + wylieBuffer.append(TibetanMachineWeb.BINDU); //append the bindu + glyphList.clear(); + } + } + } + + //replace TMW with Wylie + + if (!glyphList.isEmpty()) { + if (needsVowel) + wylieBuffer.append(withA(glyphList)); + else + wylieBuffer.append(withoutA(glyphList)); + } + + if (wylieBuffer.length() > 0) + return wylieBuffer.toString(); + else + return null; + } +} \ No newline at end of file diff --git a/org/thdl/tib/text/TibetanHTML.java b/org/thdl/tib/text/TibetanHTML.java new file mode 100644 index 0000000..0e0af2c --- /dev/null +++ b/org/thdl/tib/text/TibetanHTML.java @@ -0,0 +1,225 @@ +package org.thdl.tib.text; + +import java.util.StringTokenizer; + +public class TibetanHTML { + static String[] styleNames = + {"tmw","tmw1","tmw2","tmw3","tmw4","tmw5","tmw6","tmw7","tmw8","tmw9"}; + + public static String getStyles(String fontSize) { + return ".tmw {font: "+fontSize+"pt TibetanMachineWeb}\n"+ + ".tmw1 {font: "+fontSize+"pt TibetanMachineWeb1}\n"+ + ".tmw2 {font: "+fontSize+"pt TibetanMachineWeb2}\n"+ + ".tmw3 {font: "+fontSize+"pt TibetanMachineWeb3}\n"+ + ".tmw4 {font: "+fontSize+"pt TibetanMachineWeb4}\n"+ + ".tmw5 {font: "+fontSize+"pt TibetanMachineWeb5}\n"+ + ".tmw6 {font: "+fontSize+"pt TibetanMachineWeb6}\n"+ + ".tmw7 {font: "+fontSize+"pt TibetanMachineWeb7}\n"+ + ".tmw8 {font: "+fontSize+"pt TibetanMachineWeb8}\n"+ + ".tmw9 {font: "+fontSize+"pt TibetanMachineWeb9}\n"; + } + + public static String getHTMLX(String wylie) { + try { + StringBuffer buffer = new StringBuffer(); + for (StringTokenizer tokenizer = new StringTokenizer(wylie, " \t\n", true); tokenizer.hasMoreElements();) { + String next = tokenizer.nextToken(); + if (next.equals("\t") || next.equals("\n")) { + buffer.append(""); + buffer.append(getHTML(TibetanDocument.getTibetanMachineWeb("_"))); + buffer.append(""); + } + else + buffer.append(getHTML(TibetanDocument.getTibetanMachineWeb(next))); + } + return buffer.toString(); + } catch (InvalidWylieException ive) { + return ""; + } + } + + public static String getHTMLX(TibetanDocument.DuffData[] duffData) { + String[] styleNames = + {"tmw","tmw1","tmw2","tmw3","tmw4","tmw5","tmw6","tmw7","tmw8","tmw9"}; + + StringBuffer htmlBuffer = new StringBuffer(); + htmlBuffer.append(""); + + for (int i=0; i"); + + if (c[k] > 32 && c[k] < 127) { //ie if it's not formatting + switch (c[k]) { + case '"': + htmlBuffer.append("""); + break; + case '<': + htmlBuffer.append("<"); + break; + case '>': + htmlBuffer.append(">"); + break; + case '&': + htmlBuffer.append("&"); + break; + default: + htmlBuffer.append(c[k]); + break; + } + htmlBuffer.append(""); + String wylie = TibetanMachineWeb.getWylieForGlyph(duffData[i].font, c[k]); + if (TibetanMachineWeb.isWyliePunc(wylie)) + htmlBuffer.append(""); + } else { + htmlBuffer.append("
"); + } + } + } + + htmlBuffer.append("
"); + return htmlBuffer.toString(); + } + + public static String getIndentedHTML(String wylie) { + return getHTML("_" + wylie); + } + + public static String getHTML(String wylie) { + try { + StringBuffer buffer = new StringBuffer(); + for (StringTokenizer tokenizer = new StringTokenizer(wylie, " \t\n", true); tokenizer.hasMoreElements();) { + String next = tokenizer.nextToken(); + if (next.equals("\t") || next.equals("\n")) { + buffer.append(""); + buffer.append(getHTML(TibetanDocument.getTibetanMachineWeb("_"))); + buffer.append(""); + } + else + buffer.append(getHTML(TibetanDocument.getTibetanMachineWeb(next))); + } + return buffer.toString(); + } catch (InvalidWylieException ive) { + return ""; + } + } + + public static String getHTML(TibetanDocument.DuffData[] duffData) { + String[] styleNames = + {"tmw","tmw1","tmw2","tmw3","tmw4","tmw5","tmw6","tmw7","tmw8","tmw9"}; + + StringBuffer htmlBuffer = new StringBuffer(); + htmlBuffer.append(""); + + for (int i=0; i"); + char[] c = duffData[i].text.toCharArray(); + for (int k=0; k 31 && c[k] < 127) { //ie if it's not formatting + switch (c[k]) { + case '"': + htmlBuffer.append("""); + break; + case '<': + htmlBuffer.append("<"); + break; + case '>': + htmlBuffer.append(">"); + break; + case '&': + htmlBuffer.append("&"); + break; + default: + htmlBuffer.append(c[k]); + break; + } + String wylie = TibetanMachineWeb.getWylieForGlyph(duffData[i].font, c[k]); + if (TibetanMachineWeb.isWyliePunc(wylie)) + htmlBuffer.append(""); + } else { + htmlBuffer.append("
"); + } + } + htmlBuffer.append(""); + } + + htmlBuffer.append("
"); + return htmlBuffer.toString(); + } + + public static String getHTMLforJava(String wylie) { + //differences: + // as of 1.4.1, anyway, browser built into java does not accept and
, + // only and
+ + try { + StringBuffer buffer = new StringBuffer(); + for (StringTokenizer tokenizer = new StringTokenizer(wylie, " \t\n", true); tokenizer.hasMoreElements();) { + String next = tokenizer.nextToken(); + if (next.equals("\t") || next.equals("\n")) { + buffer.append(""); + buffer.append(getHTML(TibetanDocument.getTibetanMachineWeb("_"))); + buffer.append(""); + } + else + buffer.append(getHTML(TibetanDocument.getTibetanMachineWeb(next))); + } + return buffer.toString(); + } catch (InvalidWylieException ive) { + return ""; + } + } + + public static String getHTMLforJava(TibetanDocument.DuffData[] duffData) { + String[] fontNames = { + "TibetanMachineWeb","TibetanMachineWeb1", "TibetanMachineWeb2", + "TibetanMachineWeb3","TibetanMachineWeb4","TibetanMachineWeb5", + "TibetanMachineWeb6","TibetanMachineWeb7","TibetanMachineWeb8", + "TibetanMachineWeb9"}; + + StringBuffer htmlBuffer = new StringBuffer(); + htmlBuffer.append(""); + + for (int i=0; i"); + char[] c = duffData[i].text.toCharArray(); + for (int k=0; k 31 && c[k] < 127) { //ie if it's not formatting + switch (c[k]) { + case '"': + htmlBuffer.append("""); + break; + case '<': + htmlBuffer.append("<"); + break; + case '>': + htmlBuffer.append(">"); + break; + case '&': + htmlBuffer.append("&"); + break; + default: + htmlBuffer.append(c[k]); + break; + } + String wylie = TibetanMachineWeb.getWylieForGlyph(duffData[i].font, c[k]); + if (TibetanMachineWeb.isWyliePunc(wylie)) + htmlBuffer.append(""); + } else { + htmlBuffer.append("
"); + } + } + htmlBuffer.append(""); + } + + htmlBuffer.append("
"); + return htmlBuffer.toString(); + } +} \ No newline at end of file diff --git a/org/thdl/tib/text/TibetanKeyboard.java b/org/thdl/tib/text/TibetanKeyboard.java new file mode 100644 index 0000000..4d6c6bb --- /dev/null +++ b/org/thdl/tib/text/TibetanKeyboard.java @@ -0,0 +1,391 @@ +/* +The contents of this file are subject to the THDL Open Community License +Version 1.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License on the THDL web site +(http://www.thdl.org/). + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +License for the specific terms governing rights and limitations under the +License. + +The Initial Developer of this software is the Tibetan and Himalayan Digital +Library (THDL). Portions created by the THDL are Copyright 2001 THDL. +All Rights Reserved. + +Contributor(s): ______________________________________. +*/ + +package org.thdl.tib.text; + +import java.util.*; +import java.io.*; +import java.lang.*; +import java.net.URL; + +/** +* An alternate (non-Extended Wylie) keyboard input +* method. A keyboard URL is passed to its constructor. This URL +* must follow a particular format, and include particular subparts. +* For example, the keyboard URL must specify values for various +* input parameters, as well as correspondences for the Wylie +* characters this keyboard allows the user to input. For an example, +* see the file 'sambhota_keyboard.ini' found in the same +* directory as this class. +*

+* It is normative practice for a null keyboard to be +* interpreted as the default Wylie keyboard. +* A non-null keyboard defines a transformation of the default +* Wylie keyboard, setting new values for each Wylie value, as +* well as (re-)defining various parameters. +* +* @author Edward Garrett, Tibetan and Himalayan Digital Library +* @version 1.0 +*/ +public class TibetanKeyboard { + private boolean hasDisambiguatingKey; + private char disambiguatingKey; + private boolean hasSanskritStackingKey; + private boolean hasTibetanStackingKey; + private boolean isStackingMedial; + private char stackingKey; + private boolean isAChenRequiredBeforeVowel; + private boolean isAChungConsonant; + private boolean hasAVowel; + private Map charMap; + private Map vowelMap; + private Map puncMap; + private int command; + private final int NO_COMMAND = 0; + private final int PARAMETERS = 1; + private final int CHARACTERS = 2; + private final int VOWELS = 3; + private final int PUNCTUATION = 4; + +/** +* A generic Exception to indicate an invalid keyboard. +*/ + public class InvalidKeyboardException extends Exception { + } + +/** +* Opens the URL specified by the parameter, +* and tries to construct a keyboard from it. If the file is +* missing, or is invalid, an InvalidKeyboardException is +* thrown. +* +* @param url the URL of the keyboard +* @throws InvalidKeyboardException a valid keyboard cannot be +* constructed from this URL +*/ + public TibetanKeyboard(URL url) throws InvalidKeyboardException { + try { + InputStreamReader isr = new InputStreamReader(url.openStream()); + BufferedReader in = new BufferedReader(isr); + + System.out.println("reading "+url.toString()); + String line; + + charMap = new HashMap(); + vowelMap = new HashMap(); + puncMap = new HashMap(); + + command = NO_COMMAND; + + boolean bool; + + while ((line = in.readLine()) != null) { + + if (line.startsWith("")) + command = PARAMETERS; + + else if (line.equalsIgnoreCase("")) + command = CHARACTERS; + + else if (line.equalsIgnoreCase("")) + command = VOWELS; + + else if (line.equalsIgnoreCase("")) + command = PUNCTUATION; + } + else if (line.equals("")) //empty string + ; + + else { + StringTokenizer st = new StringTokenizer(line,"="); + String left = null, right = null; + + if (st.hasMoreTokens()) + left = st.nextToken(); + + if (st.hasMoreTokens()) + right = st.nextToken(); + + switch (command) { + case NO_COMMAND: + break; + + case PARAMETERS: + if (left == null) + throw new InvalidKeyboardException(); + + if (right == null) + break; + + if (left.equals("stack key")) { + stackingKey = right.charAt(0); + break; + } + + if (left.equals("disambiguating key")) { + disambiguatingKey = right.charAt(0); + break; + } + + bool = new Boolean(right).booleanValue(); + + if (left.equalsIgnoreCase("has sanskrit stacking")) + hasSanskritStackingKey = bool; + + if (left.equalsIgnoreCase("has tibetan stacking")) + hasTibetanStackingKey = bool; + + if (left.equalsIgnoreCase("is stacking medial")) + isStackingMedial = bool; + + if (left.equalsIgnoreCase("has disambiguating key")) + hasDisambiguatingKey = bool; + + if (left.equalsIgnoreCase("needs achen before vowels")) + isAChenRequiredBeforeVowel = bool; + + if (left.equalsIgnoreCase("has 'a' vowel")) + hasAVowel = bool; + + if (left.equalsIgnoreCase("is achung consonant")) + isAChungConsonant = bool; + + break; + + case CHARACTERS: + if (left == null) + throw new InvalidKeyboardException(); + + if (right == null) + break; + + charMap.put(right, left); + break; + + case VOWELS: + if (left == null) + throw new InvalidKeyboardException(); + + if (right == null) + break; + + vowelMap.put(right, left); + break; + + case PUNCTUATION: + if (left == null) + throw new InvalidKeyboardException(); + + if (right == null) + break; + + puncMap.put(right, left); + break; + } + } + } + } + catch (Exception e) { + throw new InvalidKeyboardException(); + } + } + +/** +* Does this keyboard have a disambiguating key? +* @return true if this keyboard has a disambiguating key, e.g. +* the period in Wylie 'g.y' vs. Wylie 'gy', false otherwise +*/ + public boolean hasDisambiguatingKey() { + return hasDisambiguatingKey; + } + +/** +* Gets the disambiguating key for this keyboard. +* @return the disambiguating key, assuming this keyboard has one +*/ + public char getDisambiguatingKey() { + return disambiguatingKey; + } + +/** +* Does this keyboard require a stacking key for Sanskrit stacks? +* @return true if this keyboard requires a +* stacking key for Sanskrit stacks +*/ + public boolean hasSanskritStackingKey() { + return hasSanskritStackingKey; + } + +/** +* Does this keyboard require a stacking key for Tibetan stacks? +* @return true if this keyboard requires a +* stacking key for Tibetan stacks +*/ + public boolean hasTibetanStackingKey() { + return hasTibetanStackingKey; + } + +/** +* Is stacking medial? +* @return true if this keyboard has stacking, and +* if that stacking is medial rather than pre/post. +* In other words, if you want a stack consisting +* of the (Wylie) characters 's', 'g', and 'r', and +* if the stack key is '+', then if you get the +* stack by typing 's+g+r', then this method returns +* true. If you get it by typing '+sgr' or '+sgr+', +* then the method returns false. +*/ + public boolean isStackingMedial() { + return isStackingMedial; + } + +/** +* Gets the stacking key. +* @return the stacking key, if there is one +*/ + public char getStackingKey() { + return stackingKey; + } + +/** +* Must achen be typed first if you want achen plus a vowel? +* @return true if it is necessary in this keyboard to +* type achen plus a vowel to get achen plus a vowel, +* or if you can (as in Wylie), simply type the vowel, +* and then automatically get achen plus the vowel, +* assuming there is no preceding consonant. +*/ + public boolean isAChenRequiredBeforeVowel() { + return isAChenRequiredBeforeVowel; + } + +/** +* Is achung treated as an ordinary consonant? +* @return true if achung is counted as a consonant, +* and thus treated as stackable like any other +* consonant; false if achung is treated as a vowel, +* as in Wylie. +*/ + public boolean isAChungConsonant() { + return isAChungConsonant; + } + +/** +* Does the keyboard have a key for the invisible 'a' vowel? +* @return true if this keyboard has a keystroke +* sequence for the invisible Wylie vowel 'a', false +* if there is no way to type this invisible vowel. +*/ + public boolean hasAVowel() { + return hasAVowel; + } + +/** +* Decides whether or not a string is a character in this keyboard. +* @return true if the parameter is a character +* in this keyboard. This method checks to see +* if the passed string has been mapped to a +* Wylie character - if not, then it returns false. +* +* @param s the possible character +*/ + public boolean isChar(String s) { + if (charMap.containsKey(s)) + return true; + else + return false; + } +/** +* Gets the Extended Wylie corresponding to this character. +* @return the Wylie value corresponding to this +* parameter, assuming it is in fact a character +* in this keyboard; if not, returns null. +* +* @param the possible character +*/ + public String getWylieForChar(String s) { + if (!charMap.containsKey(s)) + return null; + + return (String)charMap.get(s); + } + +/** +* Decides whether or not a string is a punctuation mark in this keyboard? +* @return true if the parameter is punctuation +* in this keyboard. This method checks to see if the +* passed string has been mapped to Wylie punctuation - +* if not, then it returns false. +* +* @param s the possible punctuation +*/ + public boolean isPunc(String s) { + if (puncMap.containsKey(s)) + return true; + else + return false; + } + +/** +* Gets the Extended Wylie corresponding to this punctuation. +* @return the Wylie value corresponding to this +* parameter, assuming it is in fact punctuation +* in this keyboard; if not, returns null. +* +* @param s the possible punctuation +*/ + public String getWylieForPunc(String s) { + if (!puncMap.containsKey(s)) + return null; + + return (String)puncMap.get(s); + } + +/** +* Decides whether or not the string is a vowel in this keyboard. +* @return true if the parameter is a vowel +* in this keyboard. This method checks to see if the +* passed string has been mapped to a Wylie vowel - +* if not, then it returns false. +* +* @param s the possible vowel +*/ + public boolean isVowel(String s) { + if (vowelMap.containsKey(s)) + return true; + else + return false; + } + +/** +* Gets the Extended Wylie corresponding to this vowel. +* @return the Wylie value corresponding to this +* parameter, assuming it is in fact a vowel +* in this keyboard; if not, returns null. +* +* @param the possible vowel +*/ + public String getWylieForVowel(String s) { + if (!vowelMap.containsKey(s)) + return null; + + return (String)vowelMap.get(s); + } +} \ No newline at end of file diff --git a/org/thdl/tib/text/TibetanMachineWeb.java b/org/thdl/tib/text/TibetanMachineWeb.java new file mode 100644 index 0000000..03ab80e --- /dev/null +++ b/org/thdl/tib/text/TibetanMachineWeb.java @@ -0,0 +1,1128 @@ +/* +The contents of this file are subject to the THDL Open Community License +Version 1.0 (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License on the THDL web site +(http://www.thdl.org/). + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +License for the specific terms governing rights and limitations under the +License. + +The Initial Developer of this software is the Tibetan and Himalayan Digital +Library (THDL). Portions created by the THDL are Copyright 2001 THDL. +All Rights Reserved. + +Contributor(s): ______________________________________. +*/ + +package org.thdl.tib.text; + +import java.util.*; +import java.net.URL; +import java.io.*; +import java.lang.*; +import java.awt.Font; +import java.awt.event.KeyEvent; +import javax.swing.text.*; +import java.awt.font.*; + +/** +* Interfaces between Extended Wylie and the TibetanMachineWeb fonts. +* To do this this must first read the code table, which lives in "tibwn.ini", +* and which must be found in the same directory as this class. +* @author Edward Garrett, Tibetan and Himalayan Digital Library +* @version 1.0 +*/ +public class TibetanMachineWeb { + private static boolean hasReadData = false; + private static TibetanKeyboard keyboard = null; + private static final String DEFAULT_KEYBOARD = "default_keyboard.ini"; + private static Set charSet = null; + private static Set vowelSet = null; + private static Set puncSet = null; + private static Set leftSet = null; + private static Set rightSet = null; + private static Set farRightSet = null; + private static Map tibHash = new HashMap(); + private static Map binduMap = new HashMap(); + private static String[][] toHashKey = new String[11][95]; //note: 0 slot is not used + private static DuffCode[][] TMtoTMW = new DuffCode[5][255-32]; + private static String fileName = "tibwn.ini"; + private static final String DELIMITER = "~"; + private static Set top_vowels; + private static SimpleAttributeSet[] webFontAttributeSet = new SimpleAttributeSet[11]; + private static boolean hasDisambiguatingKey; //to disambiguate gy and g.y= + private static char disambiguating_key; + private static boolean hasSanskritStackingKey; //for stacking Sanskrit + private static boolean hasTibetanStackingKey; //for stacking Tibetan + private static boolean isStackingMedial; //ie g+y, not +gy + private static char stacking_key; + private static boolean isAChenRequiredBeforeVowel; + private static boolean isAChungConsonant; + private static boolean hasAVowel; + private static String aVowel; + + public static final String[] tmFontNames = { + null, + "TibetanMachine", + "TibetanMachineSkt1", + "TibetanMachineSkt2", + "TibetanMachineSkt3", + "TibetanMachineSkt4" + }; + public static final String[] tmwFontNames = { + null, + "TibetanMachineWeb", + "TibetanMachineWeb1", + "TibetanMachineWeb2", + "TibetanMachineWeb3", + "TibetanMachineWeb4", + "TibetanMachineWeb5", + "TibetanMachineWeb6", + "TibetanMachineWeb7", + "TibetanMachineWeb8", + "TibetanMachineWeb9" + }; +/** +* the Wylie for bindu/anusvara +*/ + public static final char BINDU = 'M'; +/** +* the Wylie for tsheg +*/ + public static final char TSHEG = ' '; //this character occurs in all ten TMW fonts +/** +* the Wylie for whitespace +*/ + public static final char SPACE = '_'; //this character occurs in all ten TMW fonts +/** +* the Sanskrit stacking separator used in Extended Wylie +*/ + public static final char WYLIE_SANSKRIT_STACKING_KEY = '+'; +/** +* the Wylie disambiguating key, as a char +*/ + public static final char WYLIE_DISAMBIGUATING_KEY = '.'; +/** +* the Wylie for the invisible 'a' vowel +*/ + public static final String WYLIE_aVOWEL = "a"; +/** +* the Wylie for achung +*/ + public static final String ACHUNG = "'"; +/** +* the Wylie for achen +*/ + public static final String ACHEN = "a"; +/** +* the Wylie for gigu +*/ + public static final String i_VOWEL = "i"; +/** +* the Wylie for zhebju +*/ + public static final String u_VOWEL = "u"; +/** +* the Wylie for drengbu +*/ + public static final String e_VOWEL = "e"; +/** +* the Wylie for naro +*/ + public static final String o_VOWEL = "o"; +/** +* the Wylie for double drengbu +*/ + public static final String ai_VOWEL = "ai"; +/** +* the Wylie for double naro +*/ + public static final String au_VOWEL = "au"; +/** +* the Wylie for the subscript achung vowel +*/ + public static final String A_VOWEL = "A"; +/** +* the Wylie for log yig gigu +*/ + public static final String reverse_i_VOWEL = "-i"; +/** +* the Wylie for the vowel achung + gigu +*/ + public static final String I_VOWEL = "I"; +/** +* the Wylie for the vowel achung + zhebju +*/ + public static final String U_VOWEL = "U"; +/** +* the Wylie for the vowel achung + log yig gigu +*/ + public static final String reverse_I_VOWEL = "-I"; +/** +* represents where in an array of DuffCodes you +* find the TibetanMachine equivalence of a glyph +*/ + public static final int TM = 0; +/** +* represents where in an array of DuffCodes you +* find the reduced character equivalent of a TMW glyph +*/ + public static final int REDUCED_C = 1; +/** +* represents where in an array of DuffCodes you +* find the TibetanMachineWeb glyph +*/ + public static final int TMW = 2; +/** +* represents where in an array of DuffCodes you +* find the gigu value for a given glyph +*/ + public static final int VOWEL_i = 3; +/** +* represents where in an array of DuffCodes you +* find the zhebju value for a given glyph +*/ + public static final int VOWEL_u = 4; +/** +* represents where in an array of DuffCodes you +* find the drengbu value for a given glyph +*/ + public static final int VOWEL_e = 5; +/** +* represents where in an array of DuffCodes you +* find the naro value for a given glyph +*/ + public static final int VOWEL_o = 6; +/** +* represents where in an array of DuffCodes you +* find the achung value for a given glyph +*/ + public static final int VOWEL_A = 7; +/** +* represents where in an array of DuffCodes you +* find the achung + zhebju value for a given glyph +*/ + public static final int VOWEL_U = 8; +/** +* represents where in an array of DuffCodes you +* find the Unicode equivalence of a given glyph +*/ + public static final int UNICODE = 9; +/** +* represents where in an array of DuffCodes you +* find the half height equivalence of a given glyph +*/ + public static final int HALF_C = 10; + + private static final String lefts = "g,d,b,m,'"; + private static final String rights = "g,ng,d,n,b,m,r,l,s,',T"; + private static final String farrights = "d,s,ng"; + + static { + readData(); + + URL keyboard_url = TibetanMachineWeb.class.getResource(DEFAULT_KEYBOARD); + if (null != keyboard_url) { + try { + TibetanKeyboard kb = new TibetanKeyboard(keyboard_url); + setKeyboard(kb); + } + catch (TibetanKeyboard.InvalidKeyboardException ike) { + System.out.println("invalid keyboard file or file not found"); + setKeyboard(keyboard); + } + } + else + setKeyboard(keyboard); + } + +/* +* This method reads the data file ("tibwn.ini"), constructs +* the character, punctuation, and vowel lists, as well as +* performing other acts of initialization. +*/ + private static void readData() { + webFontAttributeSet[0] = null; + for (int i=1; i")) { + isSanskrit = false; + hashOn = false; + line = in.readLine(); + charSet = new HashSet(); + StringTokenizer st = new StringTokenizer(line,","); + while (st.hasMoreTokens()) + charSet.add(st.nextToken()); + } + else if (line.equalsIgnoreCase("")) { + isSanskrit = false; + hashOn = false; + line = in.readLine(); + vowelSet = new HashSet(); + StringTokenizer st = new StringTokenizer(line,","); + while (st.hasMoreTokens()) + vowelSet.add(st.nextToken()); + } + else if (line.equalsIgnoreCase("")) { + isSanskrit = false; + hashOn = false; + line = in.readLine(); + puncSet = new HashSet(); + StringTokenizer st = new StringTokenizer(line,","); + while (st.hasMoreTokens()) + puncSet.add(st.nextToken()); + } + + else if (line.equalsIgnoreCase("") + || line.equalsIgnoreCase("") + || line.equalsIgnoreCase("")) { + isSanskrit = false; + hashOn = true; + ignore = false; + } + else if (line.equalsIgnoreCase("")) { + isSanskrit = true; + hashOn = true; + ignore = false; + } + else if (line.equalsIgnoreCase("")) { + isSanskrit = false; + hashOn = false; + ignore = false; + } + else if (line.equalsIgnoreCase("")) + ignore = true; + } + else if (line.startsWith("//")) //comment + ; + else if (line.equals("")) //empty string + ; + else if (!ignore) { + StringTokenizer st = new StringTokenizer(line,DELIMITER,true); + + String wylie = new String(); + DuffCode[] duffCodes = new DuffCode[11]; + + int k = 0; + + while (st.hasMoreTokens()) { + String val = st.nextToken(); + + if (val.equals(DELIMITER)) + k++; + + else if (!val.equals("")) { + switch (k) { + case 0: //wylie key + wylie = val; + break; + + case 1: + duffCodes[TM] = new DuffCode(val,false); + break; + + case 2: //reduced-size character if there is one + duffCodes[REDUCED_C] = new DuffCode(val,true); + break; + + case 3: //TibetanMachineWeb code + duffCodes[k-1] = new DuffCode(val,true); + TMtoTMW[duffCodes[TM].fontNum-1][duffCodes[TM].charNum-32] = duffCodes[TMW]; + break; + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + duffCodes[k-1] = new DuffCode(val,true); + break; + + case 10: //Unicode: ignore for now + break; + + case 11: //half-height character if there is one + duffCodes[HALF_C] = new DuffCode(val,true); + break; + + case 12: //special bindu-value if vowel+bindu are one glyph + DuffCode binduCode = new DuffCode(val,true); + binduMap.put(duffCodes[TMW],binduCode); + break; + } + } + } + + if (hashOn) + tibHash.put(wylie,duffCodes); + + int font = duffCodes[2].fontNum; + int code = duffCodes[2].charNum-32; + toHashKey[font][code] = wylie; + } + } + } + catch (IOException e) { + System.out.println("file Disappeared"); + } + + hasReadData = true; + } + +/** +* (Re-)sets the keyboard. +* @param kb the keyboard to be installed. If null, then the +* Extended Wylie keyboard is installed +* @return true if the keyboard was successfully set, false +* if there was an error +*/ +public static boolean setKeyboard(TibetanKeyboard kb) { + keyboard = kb; + + if (keyboard == null) { //wylie keyboard + hasDisambiguatingKey = true; + disambiguating_key = WYLIE_DISAMBIGUATING_KEY; + hasSanskritStackingKey = true; + hasTibetanStackingKey = false; + isStackingMedial = true; + stacking_key = WYLIE_SANSKRIT_STACKING_KEY; + isAChenRequiredBeforeVowel = false; + isAChungConsonant = false; + hasAVowel = true; + aVowel = WYLIE_aVOWEL; + if (!vowelSet.contains(WYLIE_aVOWEL)) + vowelSet.add(WYLIE_aVOWEL); + } + else { + hasDisambiguatingKey = keyboard.hasDisambiguatingKey(); + if (hasDisambiguatingKey) + disambiguating_key = keyboard.getDisambiguatingKey(); + + hasSanskritStackingKey = keyboard.hasSanskritStackingKey(); + hasTibetanStackingKey = keyboard.hasTibetanStackingKey(); + if (hasSanskritStackingKey || hasTibetanStackingKey) { + isStackingMedial = keyboard.isStackingMedial(); + stacking_key = keyboard.getStackingKey(); + } + + isAChenRequiredBeforeVowel = keyboard.isAChenRequiredBeforeVowel(); + isAChungConsonant = keyboard.isAChungConsonant(); + hasAVowel = keyboard.hasAVowel(); + } + return true; +} + +/** +* (Re-)sets the keyboard. +* @param url the URL of the keyboard to be installed. +* If null, then the Extended Wylie keyboard is +* installed +* @return true if the keyboard was successfully set, false +* if there was an error +*/ +public static boolean setKeyboard(URL url) { + TibetanKeyboard kb; + + try { + kb = new TibetanKeyboard(url); + if (setKeyboard(kb)) + return true; + else + return false; + } + catch (TibetanKeyboard.InvalidKeyboardException ike) { + System.out.println("can't create this keyboard"); + return false; + } +} + +/** +* Gets the AttributeSet for the given TibetanMachineWeb font. +* This information is required in order to be able to put styled +* text into {@link TibetanDocument TibetanDocument}. +* @param font the number of the TibetanMachineWeb font for which +* you want the SimpleAttributeSet: TibetanMachineWeb = 1, +* TibetanMachineWeb1 = 2, TibetanMachineWeb = 3, etc. up to 10 +* @return a SimpleAttributeSet for the given font - that is, +* a way of encoding the font itself +*/ +public static SimpleAttributeSet getAttributeSet(int font) { + if (font > -1 && font < webFontAttributeSet.length) + return webFontAttributeSet[font]; + else + return null; +} + +/** +* Says whether or not the character is formatting. +* @param c the character to be checked +* @return true if c is formatting (TAB or +* ENTER), false if not +*/ +public static boolean isFormatting(char c) { + if (c < 32 || c > 126) + return true; + else + return false; +/* + if ( c == KeyEvent.VK_TAB + || c == KeyEvent.VK_ENTER) + + return true; + else + return false; +*/ +} + +/** +* Checks to see if the passed string +* is a character in the installed keyboard. +* +* @param s the string you want to check +* @return true if s is a character in the current keyboard, +* false if not +*/ +public static boolean isChar(String s) { + if (keyboard == null) { + if (charSet.contains(s)) + return true; + else + return false; + } + else + if (keyboard.isChar(s)) + return true; + else + return false; +} + +/** +* Checks to see if the passed string +* is a character in Extended Wylie. +* @param s the string to be checked +* @return true if s is a character in +* Extended Wylie transliteration, false if not +*/ +public static boolean isWylieChar(String s) { + if (charSet.contains(s)) + return true; + + return false; +} + +/** +* Checks to see if the passed string +* is punctuation in the installed keyboard. +* @param s the string you want to check +* @return true if s is punctuation in the current +* keyboard, false if not +*/ +public static boolean isPunc(String s) { + if (keyboard == null) { + if (puncSet.contains(s)) + return true; + else + return false; + } + else + if (keyboard.isPunc(s)) + return true; + else + return false; +} + +/** +* This method checks to see if the passed string +* is punctuation in Extended Wylie. +* @param s the string to be checked +* @return true if s is punctuation in +* Extended Wylie transliteration, false if not +*/ +public static boolean isWyliePunc(String s) { + if (puncSet.contains(s)) + return true; + + return false; +} + +/** +* Checks to see if the passed string +* is a vowel in the installed keyboard. +* @param s the string you want to check +* @return true if s is a vowel in the current +* keyboard, false if not +*/ +public static boolean isVowel(String s) { + if (keyboard == null) { + if (vowelSet.contains(s)) + return true; + else + return false; + } + else + if (keyboard.isVowel(s)) + return true; + else + return false; +} + +/** +* Checks to see if the passed string +* is a vowel in Extended Wylie. +* @param s the string to be checked +* @return true if s is a vowel in +* Extended Wylie transliteration, false if not +*/ +public static boolean isWylieVowel(String s) { + if (vowelSet.contains(s)) + return true; + + return false; +} + +/** +* Is this Wylie valid as a leftmost character +* in a Tibetan syllable? For example, in the +* syllable 'brgyad', 'b' is the leftmost +* character. Valid leftmost characters include +* g, d, b, and m. +* @param s the (Wylie) string to be checked +* @return true if s is a possible leftmost +* character in a Tibetan syllable, false +* if not. +*/ +public static boolean isWylieLeft(String s) { + if (keyboard != null) + s = keyboard.getWylieForChar(s); + + if (leftSet.contains(s)) + return true; + else + return false; +} + +/** +* Is this Wylie valid as a right (post-vowel) +* character in a Tibetan syllable? For example, +* in the syllable 'lags', 'g' is in the right +* character position. Valid right characters +* include g, ng, d, n, b, m, r, l, s, ', and T. +* @param s the (Wylie) string to be checked +* @return true if s is a possible right +* character in a Tibetan syllable, false +* if not. +*/ +public static boolean isWylieRight(String s) { + if (keyboard != null) + s = keyboard.getWylieForChar(s); + + if (rightSet.contains(s)) + return true; + else + return false; +} + +/** +* Is this Wylie valid as a leftmost character +* in a Tibetan syllable? +* @param s the string to be checked +* @return true if s is a possible leftmost +* character in a Tibetan syllable, false +* if not. +*/ +public static boolean isWylieFarRight(String s) { + if (keyboard != null) + s = keyboard.getWylieForChar(s); + + if (farRightSet.contains(s)) + return true; + else + return false; +} + +/** +* Converts character to its Extended Wylie correspondence. +* This assumes that the passed string is a character +* in the current keyboard. +* @param s the string to be converted +* @return the Wylie character corresponding to +* s, or null if there is no such character +* @see TibetanKeyboard +*/ +public static String getWylieForChar(String s) { + if (keyboard == null) + return s; + + return keyboard.getWylieForChar(s); +} + +/** +* Converts punctuation to its Extended Wylie correspondence. +* This assumes that the passed string is punctuation +* in the current keyboard. +* @param s the string to be converted +* @return the Wylie punctuation corresponding to +* s, or null if there is no such punctuation +* @see TibetanKeyboard +*/ +public static String getWylieForPunc(String s) { + if (keyboard == null) + return s; + + return keyboard.getWylieForPunc(s); +} + +/** +* Converts vowel to its Extended Wylie correspondence. +* This assumes that the passed string is a vowel +* in the current keyboard. +* @param s the string to be converted +* @return the Wylie vowel corresponding to +* s, or null if there is no such vowel +* @see TibetanKeyboard +*/ +public static String getWylieForVowel(String s) { + if (keyboard == null) + return s; + + return keyboard.getWylieForVowel(s); +} + +/** +* Gets the DuffCode required for a vowel, if +* affixed to the given hashKey. +* @param hashKey the key for the character the +* vowel is to be affixed to +* @param vowel the vowel you want the DuffCode for +* @return the DuffCode for the vowel in the given +* context, or null if there is no such vowel in +* the context +* @see DuffCode +*/ +public static DuffCode getVowel(String hashKey, int vowel) { + DuffCode[] dc = (DuffCode[])tibHash.get(hashKey); + + if (null == dc) + return null; + + return dc[vowel]; //either a vowel or null +} + +/** +* Checks to see if a glyph exists for this hash key. +* @param hashKey the key to be checked +* @return true if there is a glyph corresponding to +* hashKey, false if not +*/ +public static boolean hasGlyph(String hashKey) { + if (tibHash.get(hashKey)==null) + return false; + else + return true; +} + +/** +* Gets a glyph for this hash key. Hash keys are not identical to Extended +* Wylie. The hash key for a Tibetan stack separates the members of the stack +* with '-', for example, 's-g-r'. In Sanskrit stacks, '+' is used, e.g. 'g+h+g+h'. +* @param hashKey the key for which you want a DuffCode +* @return the TibetanMachineWeb DuffCode value for hashKey +* @see DuffCode +*/ +public static DuffCode getGlyph(String hashKey) { + DuffCode[] dc = (DuffCode[])tibHash.get(hashKey); + return dc[TMW]; +} + +/** +* Gets the half height character for this hash key. +* @param hashKey the key you want a half height glyph for +* @return the TibetanMachineWeb DuffCode of hashKey's +* reduced height glyph, or null if there is no such glyph +* @see DuffCode +*/ +public static DuffCode getHalfHeightGlyph(String hashKey) { + DuffCode[] dc = (DuffCode[])tibHash.get(hashKey); + if (dc == null) + return null; + + return dc[REDUCED_C]; +} + +private static DuffCode getTMtoTMW(int font, int code) { + if (code > 255-32) { + switch (code) { + case 8218-32: //sby + code = 130-32; + break; + + case 8230-32: //sgr + code = 133-32; + break; + + case 8225-32: //spr + code = 135-32; + break; + + case 8117-32: //tshw + code = 146-32; + break; + + case 8126-32: //rw + code = 149-32; + break; + + case 8482-32: //grw + code = 153-32; + break; + + default: + return null; + } + } + + return TMtoTMW[font][code]; +} + +private static int getTMFontNumber(String name) { + for (int i=1; i -1) + return hashKey; //because '+' remains part of Extended Wylie for Sanskrit stacks + + if (hashKey.charAt(0) == '-') + return hashKey; //because must be '-i' or '-I' vowels + + StringTokenizer st = new StringTokenizer(hashKey, "-"); + StringBuffer sb = new StringBuffer(); + + while (st.hasMoreTokens()) + sb.append(st.nextToken()); + + return sb.toString(); +} + +/** +* Gets the Extended Wylie value for this glyph. +* @param font the font of the TibetanMachineWeb +* glyph you want the Wylie of +* @param code the TibetanMachineWeb glyph +* you want the Wylie of +* @return the Wylie value corresponding to the +* glyph denoted by font, code +*/ +public static String getWylieForGlyph(int font, int code) { + String hashKey = getHashKeyForGlyph(font, code); + return wylieForGlyph(hashKey); +} + +/** +* Gets the Extended Wylie value for this glyph. +* @param dc the DuffCode of the glyph you want +* the Wylie of +* @return the Wylie value corresponding to the +* glyph denoted by dc +*/ +public static String getWylieForGlyph(DuffCode dc) { + String hashKey = getHashKeyForGlyph(dc); + return wylieForGlyph(hashKey); +} + +/** +* Says whether or not this glyph involves a Sanskrit stack. +* @param font the font of a TibetanMachineWeb glyph +* @param code the ASCII value of a TibetanMachineWeb glyph +* @return true if this glyph is a Sanskrit stack, +* false if not +*/ +public static boolean isSanskritStack(int font, int code) { + String val = toHashKey[font][code]; + if (val.indexOf(WYLIE_SANSKRIT_STACKING_KEY) == -1) + return false; + else + return true; +} + +/** +* Says whether or not this glyph involves a Sanskrit stack. +* @param dc the DuffCode of a TibetanMachineWeb glyph +* @return true if this glyph is a Sanskrit stack, +* false if not +*/ +public static boolean isSanskritStack(DuffCode dc) { + int font = dc.fontNum; + int code = dc.charNum-32; + + if (isSanskritStack(font, code)) + return true; + else + return false; +} + +/** +* Says whether or not this glyph involves a Tibetan stack. +* @param font the font of a TibetanMachineWeb glyph +* @param code the ASCII value of a TibetanMachineWeb glyph +* @return true if this glyph is a Tibetan stack, +* false if not +*/ +public static boolean isStack(int font, int code) { + String val = toHashKey[font][code]; + if (val.indexOf('-') < 1) //we allow '-i' and '-I' in as vowels + return false; + else + return true; +} + +/** +* Says whether or not this glyph involves a Tibetan stack. +* @param dc the DuffCode of a TibetanMachineWeb glyph +* @return true if this glyph is a Tibetan stack, +* false if not +*/ +public static boolean isStack(DuffCode dc) { + int font = dc.fontNum; + int code = dc.charNum-32; + + if (isStack(font, code)) + return true; + else + return false; +} + +/** +* Gets the hash with information about each character and stack. +* @return a hash containing a key for each +* entity defined in Wylie, whose object is the +* DuffCode for that key +*/ +public static Map getTibHash() { + return tibHash; +} + +/** +* Gets the hash for characters that require special bindus. +* @return a hash whose keys are all vowel glyphs (DuffCodes) +* that require a special bindu, and whose objects +* are the vowel+bindu glyph (DuffCode) corresponding to each +* such vowel glyph +*/ +public static Map getBinduMap() { + return binduMap; +} + +/** +* Does the keyboard have a disambiguating key? +* @return true if the installed keyboard has a +* disambiguating key, false if not +* @see TibetanKeyboard +*/ +public static boolean hasDisambiguatingKey() { + return hasDisambiguatingKey; +} + +/** +* Gets the disambiguating key. +* @return the disambiguating key for the installed +* keyboard, or ' ' if there is no such key +* @see TibetanKeyboard +*/ +public static char getDisambiguatingKey() { + return disambiguating_key; +} + +/** +* Does the keyboard have a Sanksrit stacking key? +* @return true if a stacking key is required +* to type Sanskrit stacks, false if not +* @see TibetanKeyboard +*/ +public static boolean hasSanskritStackingKey() { + return hasSanskritStackingKey; +} + +/** +* Does the keyboard have a Tibetan stacking key? +* @return true if a stacking key is required to +* type Tibetan stacks, false if not +* @see TibetanKeyboard +*/ +public static boolean hasTibetanStackingKey() { + return hasTibetanStackingKey; +} + +/** +* Is stacking medial? +* @return true if the stacking key is medial, +* false if not, or if there is no stacking key +* @see TibetanKeyboard +*/ +public static boolean isStackingMedial() { + return isStackingMedial; +} + +/** +* Gets the stacking key. +* @return the stacking key, or ' ' if there +* isn't one +* @see TibetanKeyboard +*/ +public static char getStackingKey() { + return stacking_key; +} + +/** +* Is achen required before vowels? +* @return true if you have to type achen first +* before you can get a vowel with achen, false +* if you can just type the vowel by itself +* (as in Wylie) +* @see TibetanKeyboard +*/ +public static boolean isAChenRequiredBeforeVowel() { + return isAChenRequiredBeforeVowel; +} + +/** +* Is achung treated as a consonant? +* @return true if a-chung is considered a consonant +* for the purposes of stacking, false if not +* (as in Wylie) +* @see TibetanKeyboard +*/ +public static boolean isAChungConsonant() { + return isAChungConsonant; +} + +/** +* Is there a key for the invisible 'a' vowel in this keyboard? +* @return true if the installed keyboard has a +* dummy a vowel, false if not +* @see TibetanKeyboard +*/ +public static boolean hasAVowel() { + return hasAVowel; +} + +/** +* Gets the invisible 'a' vowel. +* @return the dummy 'a'-vowel for the installed +* keyboard, or "" if there is no such vowel +* @see TibetanKeyboard +*/ +public static String getAVowel() { + return aVowel; +} + +/** +* Is this glyph a top (superscript) vowel? +* @param a DuffCode representing a TibetanMachineWeb +* glyph +* @return true if the glyph is a top-hanging +* (superscript) vowel (i, u, e, o, ai, or ao) +* and false if not +*/ +public static boolean isTopVowel(DuffCode dc) { + String wylie = getWylieForGlyph(dc); + if (top_vowels.contains(wylie)) + return true; + + return false; +} +} \ No newline at end of file diff --git a/org/thdl/tib/text/package.html b/org/thdl/tib/text/package.html new file mode 100644 index 0000000..7df6b80 --- /dev/null +++ b/org/thdl/tib/text/package.html @@ -0,0 +1,38 @@ + + + + + + + +Provides classes and methods for dealing with Tibetan text. +

+Designed for use with the Tibetan Computer +Company's free cross-platform TibetanMachineWeb fonts, this package +contains methods for getting the Extended Wylie +correspondences for each TibetanMachineWeb glyph, and for +convert back and forth between Extended +Wylie and TibetanMachineWeb. +

+This package provides a variety of ways to store TibetanMachineWeb data, +and includes methods to aid programmers who want to convert from +Extended Wylie to HTML or other formats. +

+Here, you can also find methods for installing and managing Tibetan +keyboards. Four keyboards have been provided in this release, +but users may also create their own keyboards. +

Related Documentation

+@see org.thdl.tib.input + + diff --git a/org/thdl/tib/text/sambhota_keyboard_1.ini b/org/thdl/tib/text/sambhota_keyboard_1.ini new file mode 100644 index 0000000..f533495 --- /dev/null +++ b/org/thdl/tib/text/sambhota_keyboard_1.ini @@ -0,0 +1,112 @@ +Sambhota Keymap One + + +has sanskrit stacking=true +has tibetan stacking=true +is stacking medial=false +stack key=f +has disambiguating key=false +disambiguating key= +needs achen before vowels=true +has 'a' vowel=true +is achung consonant=true + + +k=k +kh=K +g=g +ng=G +c=c +ch=C +j=j +ny=N +t=t +th=T +d=d +n=n +p=p +ph=P +b=b +m=m +ts=x +tsh=X +dz=D +w=w +zh=Z +z=z +'=' +y=y +r=r +l=l +sh=S +s=s +h=h +a=A +T=q +Th=Q +D=v +N=V +Sh=B +0=0 +1=1 +2=2 +3=3 +4=4 +5=5 +6=6 +7=7 +8=8 +9=9 +<0= +<1= +<2= +<3= +<4= +<5= +<6= +<7= +<8= +<9= +>0= +>1= +>2= +>3= +>4= +>5= +>6= +>7= +>8= +>9= + + +a=a +i=i +u=u +e=e +o=o +I= +U= +ai=E +au=O +A= +-i=I +-I= + + +_=. + = +/=, +|=- +!= +:=; +;= +@=# +#=$ +$= +%= +(=( +)=) +H=: +M=& +`=% +&=@ \ No newline at end of file diff --git a/org/thdl/tib/text/tcc_keyboard_1.ini b/org/thdl/tib/text/tcc_keyboard_1.ini new file mode 100644 index 0000000..dedc21c --- /dev/null +++ b/org/thdl/tib/text/tcc_keyboard_1.ini @@ -0,0 +1,115 @@ +Tibetan Computer Company Keyboard #1 + +Apparently the same as the Tibkey keyboard except: +- 'h' is a pre-post stack key, not a medial stack key + + +has sanskrit stacking=true +has tibetan stacking=true +is stacking medial=false +stack key=h +has disambiguating key=false +disambiguating key= +needs achen before vowels=true +has 'a' vowel=false +is achung consonant=true + + +k=q +kh=w +g=e +ng=r +c=t +ch=y +j=u +ny=i +t=o +th=p +d=[ +n=] +p=a +ph=s +b=d +m=f +ts=k +tsh=l +dz=; +w=' +zh=z +z=x +'=c +y=v +r=m +l=, +sh=. +s=/ +h=> +a=? +T=Q +Th=W +D=E +N=R +Sh=T +0=0 +1=1 +2=2 +3=3 +4=4 +5=5 +6=6 +7=7 +8=8 +9=9 +<0= +<1= +<2= +<3= +<4= +<5= +<6= +<7= +<8= +<9= +>0= +>1= +>2= +>3= +>4= +>5= +>6= +>7= +>8= +>9= + + +a= +i=g +u=j +e=b +o=n +I= +U= +ai=B +au=N +A= +-i=G +-I= + + +_= + = +/=\ +|=+ +!=| +:=% +;= +@=! +#=@ +$= +%= +(=( +)=) +H=: +M=* +`=` +&=$ \ No newline at end of file diff --git a/org/thdl/tib/text/tcc_keyboard_2.ini b/org/thdl/tib/text/tcc_keyboard_2.ini new file mode 100644 index 0000000..7e11087 --- /dev/null +++ b/org/thdl/tib/text/tcc_keyboard_2.ini @@ -0,0 +1,112 @@ +Tibetan Computer Company Keyboard #2 + + +has sanskrit stacking=true +has tibetan stacking=true +is stacking medial=false +stack key=a +has disambiguating key=false +disambiguating key= +needs achen before vowels=true +has 'a' vowel=false +is achung consonant=true + + +k=q +kh=w +g=s +ng=e +c=b +ch=n +j=m +ny=, +t=o +th=p +d=j +n=k +p=r +ph=/ +b=d +m=f +ts=; +tsh=' +dz=[ +w=] +zh=z +z=x +'=c +y=g +r=h +l=v +sh=. +s=l +h=G +a=H +T=O +Th=P +D=J +N=K +Sh=> +0=0 +1=1 +2=2 +3=3 +4=4 +5=5 +6=6 +7=7 +8=8 +9=9 +<0= +<1= +<2= +<3= +<4= +<5= +<6= +<7= +<8= +<9= +>0= +>1= +>2= +>3= +>4= +>5= +>6= +>7= +>8= +>9= + + +a= +i=t +u=u +e=y +o=i +I= +U= +ai=Y +au=I +A= +-i=T +-I= + + +_= + = +/=\ +|=+ +!=| +:=% +;= +@=! +#=@ +$= +%= +(=( +)=) +H=: +M=* +`=` +&=$ \ No newline at end of file diff --git a/org/thdl/tib/text/tibwn.ini b/org/thdl/tib/text/tibwn.ini new file mode 100644 index 0000000..450e2d7 --- /dev/null +++ b/org/thdl/tib/text/tibwn.ini @@ -0,0 +1,1015 @@ +// timwn.ini +// the code table for tony duff's TibetanMachineWeb +// +// format: +// - initial // marks a comment +// - blank lines should be ignored +// - marks a command +// - the commands are: +// Consonants - set of consonants in tibetan +// Vowels - set of vowels +// Other - other characters: numbers, punctuation, etc. +// Input - those codes which serve basis for wylie input method +// subtypes: Input:Punctuation, Input:Vowels, Input:Tibetan, Input:Sanskrit +// ToWylie - codes only needed for duff to wylie conversion, including vowels +// Ignore - ignore until another command is reached + + +k,kh,g,ng,c,ch,j,ny,t,th,d,n,p,ph,b,m,ts,tsh,dz,w,zh,z,',y,r,l,sh,s,h,a,T,Th,D,N,Sh,v,f,Dz,0,1,2,3,4,5,6,7,8,9,>0,>1,>2,>3,>4,>5,>6,>7,>8,>9,<0,<1,<2,<3,<4,<5,<6,<7,<8,<9 + + +a,i,u,e,o,I,U,ai,au,A,-i,-I + + +_, ,/,|,!,:,;,@,#,$,%,(,),H,M,`,& + + +//_~32,1~0,32 +// ~45,1~0,45~~~~~~~0F0B +_~32,1~~1,32 + ~45,1~~1,45~~~~~~~0F0B +/~202,1~~1,107~~~~~~~0F0D +|~203,1~~1,103~~~~~~~0F11 +!~204,1~~1,104~~~~~~~0F08 +:~206,1~~1,105~~~~~~~0F14 +;~40,5~~9,43~~~~~~~0F0F +@~210,1~~9,40~~~~~~~0F04 +#~200,1~~9,41~~~~~~~0F05 +$~38,5~~9,38~~~~~~~0F06 +%~39,5~~9,39~~~~~~~0F07 +(~208,1~~9,93~~~~~~~0F3C +)~209,1~~9,94~~~~~~~0F3D +H~239,1~~8,92~~~~~~~0F7F +M~238,1~~8,91~~~~~~~0F7E +`~241,1~~8,94~~~~~~~0F83 +&~177,4~~8,93~~~~~~~0F85 + + +Dz~146,5~~10,42 +f~153,5~~10,58~1,110~1,223~1,125~1,126~10,114~10,123 +v~154,5~~10,59~1,110~1,223~1,125~1,126~10,114~10,123 +k~33,1~1,92~1,33~1,109~1,111~1,123~1,125~10,118~10,120~0F40 +kh~34,1~~1,34~1,109~1,118~1,123~1,125~10,114~10,123~0F41 +g~35,1~1,93~1,35~1,109~1,111~1,123~1,125~10,118~10,120~0F42 +ng~36,1~~1,36~1,109~1,118~1,123~1,125~10,114~10,123~0F44 +c~37,1~~1,37~1,109~1,116~1,123~1,125~10,114~10,123~0F45 +ch~38,1~~1,38~1,109~1,117~1,123~1,125~10,114~10,123~0F46 +j~39,1~~1,39~1,109~1,118~1,123~1,125~10,114~10,123~0F47 +ny~40,1~1,94~1,40~1,109~1,112~1,123~1,125~10,115~10,121~0F48 +t~41,1~1,95~1,41~1,109~1,112~1,123~1,125~10,115~10,121~0F4F +th~42,1~~1,42~1,109~1,118~1,123~1,125~10,114~10,123~0F50 +d~43,1~1,96~1,43~1,109~1,111~1,123~1,125~10,118~10,120~0F51 +n~44,1~1,97~1,44~1,109~1,111~1,123~1,125~10,118~10,120~0F53 +p~253,1~~1,46~1,109~1,118~1,123~1,125~10,114~10,123~0F54 +ph~46,1~~1,47~1,109~1,118~1,123~1,125~10,114~10,123~0F55 +b~47,1~~1,48~1,109~1,118~1,123~1,125~10,114~10,123~0F56 +m~48,1~~1,49~1,109~1,118~1,123~1,125~10,114~10,123~0F58 +ts~49,1~~1,50~1,110~1,116~1,124~1,126~10,114~10,123~0F59 +tsh~50,1~~1,51~1,110~1,117~1,124~1,126~10,114~10,123~0F5A +dz~51,1~~1,52~1,110~1,118~1,124~1,126~10,114~10,123~0F5B +w~52,1~~1,53~1,109~1,119~1,123~1,125~10,118~10,124~0F5D +zh~53,1~1,98~1,54~1,109~1,111~1,123~1,125~10,118~10,120~0F5E +z~54,1~~1,55~1,109~1,118~1,123~1,125~10,114~10,123~0F5F +'~55,1~~1,56~1,109~1,118~1,123~1,125~10,114~10,123~0F60 +y~56,1~~1,57~1,109~1,118~1,123~1,125~10,114~10,123~0F61 +r~57,1~~1,58~1,109~1,118~1,123~1,125~10,114~10,123~0F62 +l~58,1~~1,59~1,109~1,118~1,123~1,125~10,114~10,123~0F63 +sh~59,1~1,99~1,60~1,109~1,111~1,123~1,125~10,118~10,120~0F64 +s~60,1~~1,61~1,109~1,118~1,123~1,125~10,114~10,123~0F66 +h~61,1~1,100~1,62~1,109~1,112~1,123~1,125~10,115~10,122~0F67~1,102 +a~62,1~~1,63~1,109~1,118~1,123~1,125~10,114~10,123~0F6A +T~170,1~~1,64~1,109~1,120~1,123~1,125~10,115~10,124~0F4A +Th~171,1~~1,65~1,109~1,118~1,123~1,125~10,114~10,123~0F4B +D~172,1~~1,66~1,109~1,120~1,123~1,125~10,115~10,124~0F4C +N~173,1~~1,67~1,109~1,118~1,123~1,125~10,115~10,124~0F4E +Sh~174,1~~1,68~1,109~1,118~1,123~1,125~10,115~10,124~0F65 +r-k~63,1~~1,70~1,109~1,121~1,123~1,125~10,115~10,124 +r-g~64,1~~1,71~1,109~1,121~1,123~1,125~10,115~10,124 +r-ng~65,1~~1,72~1,109~1,119~1,123~1,125~10,115~10,124 +r-j~66,1~~1,73~1,109~1,119~1,123~1,125~10,115~10,124 +r-ny~67,1~~1,74~1,109~1,113~1,123~1,125~10,116~10,125 +r-t~68,1~1,101~1,75~1,109~1,113~1,123~1,125~10,116~10,124 +r-d~69,1~~1,76~1,109~1,121~1,123~1,125~10,115~10,124 +r-n~70,1~~1,77~1,109~1,121~1,123~1,125~10,115~10,124 +r-b~71,1~~1,78~1,109~1,119~1,123~1,125~10,115~10,124 +r-m~72,1~~1,79~1,109~1,119~1,123~1,125~10,115~10,124 +r-ts~73,1~~1,80~1,109~1,119~1,123~1,125~10,115~10,124 +r-dz~74,1~~1,81~1,109~1,119~1,123~1,125~10,115~10,124 +l-k~75,1~~1,82~1,109~1,114~1,123~1,125~10,116~10,125 +l-g~76,1~~1,83~1,109~1,114~1,123~1,125~10,116~10,125 +l-ng~77,1~~1,84~1,109~1,122~1,123~1,125~10,116~10,125 +l-c~78,1~~1,85~1,109~1,122~1,123~1,125~10,116~10,125 +l-j~79,1~~1,86~1,109~1,122~1,123~1,125~10,116~10,125 +l-t~80,1~~1,87~1,109~1,115~1,123~1,125~10,116~10,125 +l-d~81,1~~1,88~1,109~1,114~1,123~1,125~10,116~10,125 +l-p~82,1~~1,89~1,109~1,122~1,123~1,125~10,116~10,125 +l-b~83,1~~1,90~1,109~1,122~1,123~1,125~10,116~10,125 +l-h~84,1~~1,91~1,109~1,115~1,123~1,125~10,116~10,125 +s-k~85,1~~2,33~1,109~2,113~1,123~1,125~10,116~10,125 +s-g~86,1~~2,34~1,109~2,113~1,123~1,125~10,116~10,125 +s-ng~87,1~~2,35~1,109~2,116~1,123~1,125~10,116~10,125 +s-ny~88,1~~2,36~1,109~2,114~1,123~1,125~10,116~10,125 +s-t~89,1~~2,37~1,109~2,114~1,123~1,125~10,116~10,125 +s-d~90,1~~2,38~1,109~2,113~1,123~1,125~10,116~10,125 +s-n~91,1~~2,39~1,109~2,113~1,123~1,125~10,116~10,125 +s-p~92,1~~2,40~1,109~2,116~1,123~1,125~10,116~10,125 +s-b~93,1~~2,41~1,109~2,116~1,123~1,125~10,116~10,125 +s-m~94,1~~2,42~1,109~2,116~1,123~1,125~10,116~10,125 +s-ts~95,1~~2,43~1,109~2,116~1,123~1,125~10,116~10,125 +k-w~138,1~~2,44~1,109~2,116~1,123~1,125~10,116~10,125 +kh-w~139,1~~2,46~1,109~2,116~1,123~1,125~10,116~10,125 +g-w~140,1~~2,47~1,109~2,116~1,123~1,125~10,116~10,125 +c-w~141,1~~2,48~1,109~2,116~1,123~1,125~10,116~10,125 +ny-w~157,1~~2,49~1,109~2,116~1,123~1,125~10,116~10,125 +t-w~143,1~~2,50~1,109~2,116~1,123~1,125~10,116~10,125 +d-w~144,1~~2,51~1,109~2,116~1,123~1,125~10,116~10,125 +ts-w~145,1~~2,52~1,110~2,116~1,124~1,126~10,116~10,125 +tsh-w~146,1~~2,53~1,110~2,116~1,124~1,126~10,116~10,125 +zh-w~147,1~~2,54~1,109~2,116~1,123~1,125~10,116~10,125 +z-w~148,1~~2,55~1,109~2,116~1,123~1,125~10,116~10,125 +r-w~149,1~~2,56~1,109~2,116~1,123~1,125~10,115~10,125 +sh-w~150,1~~2,57~1,109~2,116~1,123~1,125~10,116~10,125 +s-w~151,1~~2,58~1,109~2,116~1,123~1,125~10,115~10,125 +h-w~152,1~~2,59~1,109~2,116~1,123~1,125~10,116~10,125 +k-y~96,1~~2,60~1,109~2,111~1,123~1,125~10,115~10,124 +kh-y~97,1~~2,61~1,109~2,111~1,123~1,125~10,115~10,124 +g-y~98,1~~2,62~1,109~2,111~1,123~1,125~10,115~10,124 +p-y~99,1~~2,63~1,109~2,112~1,123~1,125~10,116~10,125 +ph-y~100,1~~2,64~1,109~2,112~1,123~1,125~10,116~10,125 +b-y~101,1~~2,65~1,109~2,112~1,123~1,125~10,116~10,125 +m-y~102,1~~2,66~1,109~2,112~1,123~1,125~10,116~10,125 +k-r~103,1~~2,67~1,109~2,115~1,123~1,125~10,115~10,124 +kh-r~104,1~~2,68~1,109~2,115~1,123~1,125~10,115~10,124 +g-r~105,1~~2,69~1,109~2,115~1,123~1,125~10,115~10,124 +t-r~106,1~~2,70~1,109~2,115~1,123~1,125~10,115~10,124 +th-r~107,1~~2,71~1,109~2,115~1,123~1,125~10,115~10,124 +d-r~108,1~~2,72~1,109~2,115~1,123~1,125~10,115~10,124 +p-r~109,1~~2,73~1,109~2,115~1,123~1,125~10,115~10,124 +ph-r~110,1~~2,74~1,109~2,115~1,123~1,125~10,115~10,124 +b-r~111,1~~2,75~1,109~2,115~1,123~1,125~10,115~10,124 +m-r~112,1~~2,76~1,109~2,115~1,123~1,125~10,115~10,124 +sh-r~113,1~~2,77~1,109~2,115~1,123~1,125~10,115~10,124 +s-r~114,1~~2,78~1,109~2,115~1,123~1,125~10,115~10,124 +h-r~115,1~~2,79~1,109~2,115~1,123~1,125~10,115~10,124 +k-l~116,1~~2,80~1,109~2,116~1,123~1,125~10,116~10,125 +g-l~117,1~~2,81~1,109~2,116~1,123~1,125~10,116~10,125 +b-l~118,1~~2,82~1,109~2,116~1,123~1,125~10,116~10,125 +z-l~119,1~~2,83~1,109~2,116~1,123~1,125~10,116~10,125 +r-l~120,1~~2,84~1,109~2,116~1,123~1,125~10,116~10,125 +s-l~121,1~~2,85~1,109~2,116~1,123~1,125~10,116~10,125 +r-k-y~122,1~~2,86~1,109~2,118~1,123~1,125~10,116~10,125 +r-g-y~123,1~~2,87~1,109~2,118~1,123~1,125~10,116~10,125 +r-m-y~124,1~~2,88~1,109~2,118~1,123~1,125~10,116~10,125 +r-g-w~125,1~~2,89~1,109~2,117~1,123~1,125~10,117~10,126 +r-ts-w~126,1~~2,90~1,109~2,117~1,123~1,125~10,117~10,126 +s-k-y~254,1~~2,91~1,109~2,119~1,123~1,125~10,117~10,126 +s-g-y~128,1~~2,92~1,109~2,119~1,123~1,125~10,117~10,126 +s-p-y~129,1~~2,93~1,109~2,119~1,123~1,125~10,117~10,126 +s-b-y~130,1~~2,94~1,109~2,119~1,123~1,125~10,117~10,126 +s-m-y~131,1~~2,95~1,109~2,117~1,123~1,125~10,117~10,126 +s-k-r~132,1~~2,96~1,109~2,117~1,123~1,125~10,117~10,126 +s-g-r~133,1~~2,97~1,109~2,117~1,123~1,125~10,117~10,126 +s-n-r~134,1~~2,98~1,109~2,117~1,123~1,125~10,117~10,126 +s-p-r~135,1~~2,99~1,109~2,117~1,123~1,125~10,117~10,126 +s-b-r~136,1~~2,100~1,109~2,117~1,123~1,125~10,117~10,126 +s-m-r~137,1~~2,101~1,109~2,117~1,123~1,125~10,117~10,126 +g-r-w~153,1~~2,102~1,109~2,116~1,123~1,125~10,115~10,124 +d-r-w~154,1~~2,103~1,109~~1,123~1,125~~ +ph-y-w~155,1~~2,104~1,109~~1,123~1,125~~ + + +-i~222,1~~8,87~~~~~~~0F80~~8,98 +ai~234,1~~8,88~~~~~~~0F7B~~8,101 +au~237,1~~8,89~~~~~~~0F7D~~8,104 +//need +I as well + + +k+Sh~176,1~~1,69~1,109~1,122~1,123~1,125~10,116~10,125~0F69 +k+k~33,2~~3,33~1,109~4,120~1,123~1,125~4,106~4,113 +k+kh~34,2~~3,34~1,109~4,120~1,123~1,125~4,106~4,113 +k+ng~35,2~~3,35~1,109~4,120~1,123~1,125~4,106~4,113 +k+ts~36,2~~3,36~1,109~4,120~1,123~1,125~4,106~4,113 +k+t~37,2~~3,37~1,109~4,120~1,123~1,125~4,106~4,113 +k+t+y~38,2~~3,38~1,109~4,121~1,123~1,125~4,107~4,114 +k+t+r~39,2~~3,39~1,109~4,121~1,123~1,125~4,107~4,114 +k+t+r+y~40,2~~3,40~1,109~4,123~1,123~1,125~4,109~4,116 +k+t+w~41,2~~3,41~1,109~4,121~1,123~1,125~4,107~4,114 +k+th~42,2~~3,42~1,109~4,120~1,123~1,125~4,106~4,113 +k+th+y~43,2~~3,43~1,109~4,122~1,123~1,125~4,108~4,115 +k+N~44,2~~3,44~1,109~4,120~1,123~1,125~4,106~4,113 +k+n~252,2~~3,46~1,109~4,120~1,123~1,125~4,106~4,113 +k+n+y~46,2~~3,47~1,109~4,122~1,123~1,125~4,108~4,115 +k+ph~47,2~~3,48~1,109~4,120~1,123~1,125~4,106~4,113 +k+m~48,2~~3,49~1,109~4,120~1,123~1,125~4,106~4,113 +k+m+y~49,2~~3,50~1,109~4,122~1,123~1,125~4,108~4,115 +k+r+y~50,2~~3,51~1,109~4,120~1,123~1,125~4,106~4,113 +k+w+y~192,4~~3,52~1,109~8,121~1,123~1,125~8,107~8,114 +k+sh~51,2~~3,53~1,109~4,120~1,123~1,125~4,106~4,113 +k+s~52,2~~3,54~1,109~4,120~1,123~1,125~4,106~4,113 +k+s+n~53,2~~3,55~1,109~4,124~1,123~1,125~4,110~4,117 +k+s+m~54,2~~3,56~1,109~4,123~1,123~1,125~4,109~4,116 +k+s+y~55,2~~3,57~1,109~4,122~1,123~1,125~4,108~4,115 +k+s+w~56,2~~3,58~1,109~4,122~1,123~1,125~4,108~4,115 +kh+kh~59,2~~3,61~1,109~4,120~1,123~1,125~4,106~4,113 +kh+n~60,2~~3,62~1,109~4,120~1,123~1,125~4,106~4,113 +kh+l~61,2~~3,63~1,109~4,120~1,123~1,125~4,106~4,113 +g+g~62,2~~3,64~1,109~4,120~1,123~1,125~4,106~4,113 +g+g+h~63,2~~3,65~1,109~4,124~1,123~1,125~4,110~4,117 +g+ny~64,2~~3,66~1,109~4,120~1,123~1,125~4,106~4,113 +g+d~65,2~~3,67~1,109~4,120~1,123~1,125~4,106~4,113 +g+d+h~66,2~~3,68~1,109~4,123~1,123~1,125~4,109~4,116 +g+d+h+y~67,2~~3,69~1,109~4,125~1,123~1,125~4,111~4,118 +g+d+h+w~68,2~~3,70~1,109~4,125~1,123~1,125~4,111~4,118 +g+n~69,2~~3,71~1,109~4,120~1,123~1,125~4,106~4,113 +g+n+y~70,2~~3,72~1,109~4,122~1,123~1,125~4,108~4,115 +g+p~71,2~~3,73~1,109~4,120~1,123~1,125~4,106~4,113 +g+b+h~72,2~~3,74~1,109~4,124~1,123~1,125~4,110~4,117 +g+b+h+y~73,2~~3,75~1,109~4,125~1,123~1,125~4,111~4,118 +g+m~74,2~~3,76~1,109~4,120~1,123~1,125~4,106~4,113 +g+m+y~75,2~~3,77~1,109~4,122~1,123~1,125~4,108~4,115 +g+r+y~76,2~~3,78~1,109~4,120~1,123~1,125~4,106~4,113 +g+h~77,2~~3,79~1,109~4,120~1,123~1,125~4,106~4,113~0F43 +g+h+g+h~78,2~~3,80~1,109~4,126~1,123~1,125~4,112~4,119 +g+h+ny~79,2~~3,81~1,109~4,124~1,123~1,125~4,110~4,117 +g+h+n~80,2~~3,82~1,109~4,124~1,123~1,125~4,110~4,117 +g+h+n+y~81,2~~3,83~1,109~4,125~1,123~1,125~4,111~4,118 +g+h+m~82,2~~3,84~1,109~4,124~1,123~1,125~4,110~4,117 +g+h+l~83,2~~3,85~1,109~4,124~1,123~1,125~4,110~4,117 +g+h+y~84,2~~3,86~1,109~4,121~1,123~1,125~4,107~4,114 +g+h+r~85,2~~3,87~1,109~4,121~1,123~1,125~4,107~4,114 +g+h+w~86,2~~3,88~1,109~4,121~1,123~1,125~4,107~4,114 +ng+k~87,2~~3,89~1,109~4,120~1,123~1,125~4,106~4,113 +ng+k+t~88,2~~3,90~1,109~4,124~1,123~1,125~4,110~4,117 +ng+k+t+y~89,2~~3,91~1,109~4,125~1,123~1,125~4,111~4,118 +ng+k+y~90,2~~3,92~1,109~4,122~1,123~1,125~4,108~4,115 +ng+kh~91,2~~3,93~1,109~4,120~1,123~1,125~4,106~4,113 +ng+kh+y~92,2~~3,94~1,109~4,122~1,123~1,125~4,108~4,115 +ng+g~93,2~~3,95~1,109~4,120~1,123~1,125~4,106~4,113 +ng+g+r~94,2~~3,96~1,109~4,121~1,123~1,125~4,107~4,114 +ng+g+y~95,2~~3,97~1,109~4,122~1,123~1,125~4,108~4,115 +ng+g+h~96,2~~3,98~1,109~4,124~1,123~1,125~4,110~4,117 +ng+g+h+y~97,2~~3,99~1,109~4,125~1,123~1,125~4,111~4,118 +ng+g+h+r~98,2~~3,100~1,109~4,125~1,123~1,125~4,111~4,118 +ng+ng~99,2~~3,101~1,109~4,120~1,123~1,125~4,106~4,113 +ng+t~100,2~~3,102~1,109~4,120~1,123~1,125~4,106~4,113 +ng+n~101,2~~3,103~1,109~4,120~1,123~1,125~4,106~4,113 +ng+m~102,2~~3,104~1,109~4,120~1,123~1,125~4,106~4,113 +ng+y~103,2~~3,105~1,109~4,120~1,123~1,125~4,106~4,113 +ng+l~104,2~~3,106~1,109~4,120~1,123~1,125~4,106~4,113 +ng+sh~105,2~~3,107~1,109~4,120~1,123~1,125~4,106~4,113 +ng+h~106,2~~3,108~1,109~4,120~1,123~1,125~4,106~4,113 +ng+k+Sh~107,2~~3,109~1,109~4,123~1,123~1,125~4,109~4,116 +ng+k+Sh+w~108,2~~3,110~1,109~4,124~1,123~1,125~4,110~4,117 +ng+k+Sh+y~109,2~~3,111~1,109~4,125~1,123~1,125~4,111~4,118 +ts+ts~110,2~~3,112~1,110~4,120~1,125~1,126~4,106~4,113 +ts+tsh~111,2~~3,113~1,110~4,120~1,125~1,126~4,106~4,113 +ts+tsh+w~112,2~~3,114~1,110~4,122~1,125~1,126~4,108~4,115 +ts+tsh+r~113,2~~3,115~1,110~4,122~1,125~1,126~4,108~4,115 +ts+ny~114,2~~3,116~1,110~4,120~1,125~1,126~4,106~4,113 +ts+ny~115,2~~3,117~1,110~4,122~1,125~1,126~4,108~4,115 +ts+m~116,2~~3,118~1,110~4,120~1,125~1,126~4,106~4,113 +ts+y~117,2~~3,119~1,110~4,120~1,125~1,126~4,106~4,113 +ts+r~118,2~~3,120~1,110~4,120~1,125~1,126~4,106~4,113 +ts+l~119,2~~3,121~1,110~4,120~1,125~1,126~4,106~4,113 +ts+h+y~120,2~~3,122~1,110~4,122~1,125~1,126~4,108~4,115 +tsh+th~121,2~~3,123~1,110~4,120~1,125~1,126~4,106~4,113 +tsh+tsh~122,2~~3,124~1,110~4,120~1,125~1,126~4,106~4,113 +tsh+y~123,2~~3,125~1,110~4,120~1,125~1,126~4,106~4,113 +tsh+r~124,2~~3,126~1,110~4,120~1,125~1,126~4,106~4,113 +tsh+l~125,2~~4,33~1,110~4,120~1,125~1,126~4,106~4,113 +dz+dz~126,2~~4,34~1,110~4,120~1,125~1,126~4,106~4,113 +dz+dz+ny~253,2~~4,35~1,110~4,124~1,125~1,126~4,110~4,117 +dz+dz+w~128,2~~4,36~1,110~4,123~1,125~1,126~4,109~4,116 +dz+dz+h~129,2~~4,37~1,110~4,124~1,125~1,126~4,110~4,117 +dz+h+dz+h~130,2~~4,38~1,110~4,126~1,125~1,126~4,112~4,119 +dz+ny~131,2~~4,39~1,110~4,120~1,125~1,126~4,106~4,113 +dz+ny+y~132,2~~4,40~1,110~4,122~1,125~1,126~4,108~4,115 +dz+n~133,2~~4,41~1,110~4,120~1,125~1,126~4,106~4,113 +dz+n+w~134,2~~4,42~1,110~4,122~1,125~1,126~4,108~4,115 +dz+m~135,2~~4,43~1,110~4,120~1,125~1,126~4,106~4,113 +dz+y~136,2~~4,44~1,110~4,120~1,125~1,126~4,106~4,113 +dz+r~137,2~~4,46~1,110~4,120~1,123~1,126~4,106~4,113 +dz+w~138,2~~4,47~1,110~4,120~1,123~1,126~4,106~4,113 +dz+h~139,2~~4,48~1,110~4,120~1,123~1,126~4,106~4,113~0F5C +dz+h+y~140,2~~4,49~1,110~4,121~1,123~1,126~4,107~4,114 +dz+h+r~141,2~~4,50~1,110~4,121~1,123~1,126~4,107~4,114 +dz+h+l~249,2~~4,51~1,110~4,124~1,123~1,126~4,110~4,117 +dz+h+w~143,2~~4,52~1,110~4,122~1,123~1,126~4,108~4,115 +ny+ts~144,2~~4,53~1,109~4,120~1,123~1,125~4,106~4,113 +ny+ts+m~145,2~~4,54~1,109~4,124~1,123~1,125~4,110~4,117 +ny+ts+y~146,2~~4,55~1,109~4,122~1,123~1,125~4,108~4,115 +ny+tsh~147,2~~4,56~1,109~4,120~1,123~1,125~4,106~4,113 +ny+dz~148,2~~4,57~1,109~4,120~1,123~1,125~4,106~4,113 +ny+dz+y~149,2~~4,58~1,109~4,123~1,123~1,125~4,109~4,116 +ny+dz+h~150,2~~4,59~1,109~4,124~1,123~1,125~4,110~4,117 +ny+ny~151,2~~4,60~1,109~4,120~1,123~1,125~4,106~4,113 +ny+p~152,2~~4,61~1,109~4,120~1,123~1,125~4,106~4,113 +ny+ph~153,2~~4,62~1,109~4,120~1,123~1,125~4,106~4,113 +ny+y~154,2~~4,63~1,109~4,120~1,123~1,125~4,106~4,113 +ny+r~155,2~~4,64~1,109~4,120~1,123~1,125~4,106~4,113 +ny+l~156,2~~4,65~1,109~4,120~1,123~1,125~4,106~4,113 +ny+sh~157,2~~4,66~1,109~4,120~1,123~1,125~4,106~4,113 +T+k~250,2~~4,67~1,109~4,120~1,123~1,125~4,106~4,113 +T+T~159,2~~4,68~1,109~4,120~1,123~1,125~4,106~4,113 +T+T+h~254,2~~4,69~1,109~4,124~1,123~1,125~4,110~4,117 +T+n~188,4~~4,70~1,109~8,120~1,123~1,125~8,106~8,113 +T+p~161,2~~4,71~1,109~4,120~1,123~1,125~4,106~4,113 +T+m~162,2~~4,72~1,109~4,120~1,123~1,125~4,106~4,113 +T+y~163,2~~4,73~1,109~4,120~1,123~1,125~4,106~4,113 +T+w~164,2~~4,74~1,109~4,121~1,123~1,125~4,107~4,114 +T+s~165,2~~4,75~1,109~4,120~1,123~1,125~4,106~4,113 +Th+y~251,2~~4,76~1,109~4,120~1,123~1,125~4,106~4,113 +Th+r~167,2~~4,77~1,109~4,120~1,123~1,125~4,106~4,113 +D+g~168,2~~4,78~1,109~4,120~1,123~1,125~4,106~4,113 +D+g+y~169,2~~4,79~1,109~4,122~1,123~1,125~4,108~4,115 +D+g+h~170,2~~4,80~1,109~4,124~1,123~1,125~4,110~4,117 +D+g+h+r~171,2~~4,81~1,109~4,125~1,123~1,125~4,111~4,118 +D+D~172,2~~4,82~1,109~4,120~1,123~1,125~4,106~4,113 +D+D+h~173,2~~4,83~1,109~4,123~1,123~1,125~4,109~4,116 +D+D+h+y~174,2~~4,84~1,109~4,125~1,123~1,125~4,111~4,118 +D+n~175,2~~4,85~1,109~4,120~1,123~1,125~4,106~4,113 +D+m~176,2~~4,86~1,109~4,120~1,123~1,125~4,106~4,113 +D+y~177,2~~4,87~1,109~4,120~1,123~1,125~4,106~4,113 +D+r~178,2~~4,88~1,109~4,120~1,123~1,125~4,106~4,113 +D+w~179,2~~4,89~1,109~4,120~1,123~1,125~4,106~4,113 +D+h~180,2~~4,90~1,109~4,120~1,123~1,125~4,106~4,113~0F4D +D+h+D+h~181,2~~4,91~1,109~4,126~1,123~1,125~4,112~4,119 +D+h+m~182,2~~4,92~1,109~4,124~1,123~1,125~4,110~4,117 +D+h+y~183,2~~4,93~1,109~4,121~1,123~1,125~4,107~4,114 +D+h+r~184,2~~4,94~1,109~4,121~1,123~1,125~4,107~4,114 +D+h+w~185,2~~4,95~1,109~4,121~1,123~1,125~4,107~4,114 +N+T~186,2~~4,96~1,109~4,120~1,123~1,125~4,106~4,113 +N+Th~187,2~~4,97~1,109~4,120~1,123~1,125~4,106~4,113 +N+D~188,2~~4,98~1,109~4,120~1,123~1,125~4,106~4,113 +N+D+y~189,2~~4,99~1,109~4,123~1,123~1,125~4,109~4,116 +N+D+r~193,4~~4,100~1,109~8,121~1,123~1,125~8,107~8,114 +N+D+r+y~190,2~~4,101~1,109~4,125~1,123~1,125~4,111~4,118 +N+D+h~191,2~~4,102~1,109~4,123~1,123~1,125~4,109~4,116 +N+N~192,2~~4,103~1,109~4,120~1,123~1,125~4,106~4,113 +N+d+r~193,2~~4,104~1,109~4,122~1,123~1,125~4,108~4,115 +N+m~194,2~~4,105~1,109~4,120~1,123~1,125~4,106~4,113 +N+y~195,2~~5,33~1,109~4,120~1,123~1,125~4,106~4,113 +N+w~196,2~~5,34~1,109~4,120~1,123~1,125~4,106~4,113 +t+k~197,2~~5,35~1,109~4,120~1,123~1,125~4,106~4,113 +t+k+r~198,2~~5,36~1,109~4,121~1,123~1,125~4,107~4,114 +t+k+w~33,3~~5,37~1,109~6,122~1,123~1,125~6,108~6,115 +t+k+s~199,2~~5,38~1,109~4,123~1,123~1,125~4,109~4,116 +t+g~189,4~~5,39~1,109~8,120~1,123~1,125~8,106~8,113 +t+ny~34,3~~5,40~1,109~6,120~1,123~1,125~6,106~6,113 +t+Th~35,3~~5,41~1,109~6,120~1,123~1,125~6,106~6,113 +t+t~36,3~~5,42~1,109~6,120~1,123~1,125~6,106~6,113 +t+t+y~37,3~~5,43~1,109~6,122~1,123~1,125~6,108~6,115 +t+t+r~38,3~~5,44~1,109~6,121~1,123~1,125~6,107~6,114 +t+t+w~39,3~~5,46~1,109~6,121~1,123~1,125~6,107~6,114 +t+th~40,3~~5,47~1,109~6,120~1,123~1,125~6,106~6,113 +t+th+y~41,3~~5,48~1,109~6,122~1,123~1,125~6,108~6,115 +t+n~42,3~~5,49~1,109~6,120~1,123~1,125~6,106~6,113 +t+n+y~43,3~~5,50~1,109~6,122~1,123~1,125~6,108~6,115 +t+p~44,3~~5,51~1,109~6,120~1,123~1,125~6,106~6,113 +t+p+r~252,3~~5,52~1,109~6,122~1,123~1,125~6,108~6,115 +t+ph~46,3~~5,53~1,109~6,120~1,123~1,125~6,106~6,113 +t+m~47,3~~5,54~1,109~6,120~1,123~1,125~6,106~6,113 +t+m+y~48,3~~5,55~1,109~6,122~1,123~1,125~6,108~6,115 +t+y~49,3~~5,56~1,109~6,120~1,123~1,125~6,106~6,113 +t+rn~50,3~~5,57~1,110~6,121~1,125~1,125~6,107~6,114 +t+s~51,3~~5,58~1,110~6,120~1,125~1,125~6,106~6,113 +t+s+th~52,3~~5,59~1,110~6,124~1,125~1,125~6,110~6,117 +t+s+n~53,3~~5,60~1,109~6,124~1,123~1,125~6,110~6,117 +t+s+n+y~54,3~~5,61~1,109~6,125~1,123~1,125~6,111~6,118 +t+s+m~55,3~~5,62~1,109~6,124~1,123~1,125~6,110~6,117 +t+s+m+y~56,3~~5,63~1,109~6,125~1,123~1,125~6,111~6,118 +t+s+y~57,3~~5,64~1,109~6,122~1,123~1,125~6,108~6,115 +t+s+r~58,3~~5,65~1,109~6,122~1,123~1,125~6,108~6,115 +t+s+w~59,3~~5,66~1,109~6,122~1,123~1,125~6,108~6,115 +t+r+y~60,3~~5,67~1,109~6,121~1,123~1,125~6,107~6,114 +t+w+y~61,3~~5,68~1,109~6,122~1,123~1,125~6,108~6,115 +t+k+Sh~62,3~~5,69~1,109~6,123~1,123~1,125~6,109~6,116 +th+y~63,3~~5,70~1,109~6,120~1,123~1,125~6,106~6,113 +th+w~64,3~~5,71~1,109~6,120~1,123~1,125~6,106~6,113 +d+g~65,3~~5,72~1,109~6,120~1,123~1,125~6,106~6,113 +d+g+y~66,3~~5,73~1,109~6,122~1,123~1,125~6,108~6,115 +d+g+r~67,3~~5,74~1,109~6,122~1,123~1,125~6,108~6,115 +d+g+h~68,3~~5,75~1,109~6,124~1,123~1,125~6,110~6,117 +d+g+h+r~69,3~~5,76~1,109~6,125~1,123~1,125~6,111~6,118 +d+dz~70,3~~5,77~1,109~6,120~1,123~1,125~6,106~6,113 +d+d~71,3~~5,78~1,109~6,120~1,123~1,125~6,106~6,113 +d+d+y~72,3~~5,79~1,109~6,122~1,123~1,125~6,108~6,115 +d+d+r~73,3~~5,80~1,109~6,122~1,123~1,125~6,108~6,115 +d+d+w~74,3~~5,81~1,109~6,122~1,123~1,125~6,108~6,115 +d+d+h~75,3~~5,82~1,109~6,123~1,123~1,125~6,109~6,116 +d+d+h+n~76,3~~5,83~1,109~6,126~1,123~1,125~6,112~6,119 +d+d+h+y~77,3~~5,84~1,109~6,125~1,123~1,125~6,111~6,118 +d+d+h+r~78,3~~5,85~1,109~6,125~1,123~1,125~6,111~6,118 +d+d+h+r+w~79,3~~5,86~1,109~6,125~1,123~1,125~6,111~6,118 +d+n~80,3~~5,87~1,109~6,120~1,123~1,125~6,106~6,113 +d+b~81,3~~5,88~1,109~6,120~1,123~1,125~6,106~6,113 +d+b+r~82,3~~5,89~1,109~6,121~1,123~1,125~6,107~6,114 +d+b+h~83,3~~5,90~1,109~6,124~1,123~1,125~6,110~6,117 +d+b+h+y~84,3~~5,91~1,109~6,125~1,123~1,125~6,111~6,118 +d+b+h+r~85,3~~5,92~1,109~6,125~1,123~1,125~6,111~6,118 +d+m~86,3~~5,93~1,109~6,120~1,123~1,125~6,106~6,113 +d+y~87,3~~5,94~1,109~6,120~1,123~1,125~6,106~6,113 +d+r+y~88,3~~5,95~1,109~6,121~1,123~1,125~6,107~6,114 +d+w+y~89,3~~5,96~1,109~6,122~1,123~1,125~6,108~6,115 +d+h~90,3~~5,97~1,109~6,120~1,123~1,125~6,106~6,113~0F4D +d+h+n~91,3~~5,98~1,109~6,124~1,123~1,125~6,110~6,117 +d+h+n+y~92,3~~5,99~1,109~6,125~1,123~1,125~6,111~6,118 +d+h+m~93,3~~5,100~1,109~6,124~1,123~1,125~6,110~6,117 +d+h+y~94,3~~5,101~1,109~6,122~1,123~1,125~6,108~6,115 +d+h+r~95,3~~5,102~1,109~6,121~1,123~1,125~6,107~6,114 +d+h+r+y~96,3~~5,103~1,109~6,123~1,123~1,125~6,109~6,116 +d+h+w~97,3~~5,104~1,109~6,121~1,123~1,125~6,107~6,114 +n+k~98,3~~5,105~1,109~6,120~1,123~1,125~6,106~6,113 +n+k+t~99,3~~5,106~1,109~6,124~1,123~1,125~6,110~6,117 +n+g+h~101,3~~5,107~1,109~6,124~1,123~1,125~6,110~6,117 +n+ng~102,3~~5,108~1,109~6,120~1,123~1,125~6,106~6,113 +n+dz~103,3~~5,109~1,109~6,120~1,123~1,125~6,106~6,113 +n+dz+y~104,3~~5,110~1,109~6,123~1,123~1,125~6,109~6,116 +n+D~105,3~~5,111~1,109~6,120~1,123~1,125~6,106~6,113 +n+t~106,3~~5,112~1,109~6,120~1,123~1,125~6,106~6,113 +n+t+y~107,3~~5,113~1,109~6,122~1,123~1,125~6,108~6,115 +n+t+r~108,3~~5,114~1,109~6,121~1,123~1,125~6,107~6,114 +n+t+r+y~109,3~~5,115~1,109~6,123~1,123~1,125~6,109~6,116 +n+t+w~110,3~~5,116~1,109~6,121~1,123~1,125~6,107~6,114 +n+t+s~111,3~~5,117~1,109~6,123~1,123~1,125~6,109~6,116 +n+th~112,3~~5,118~1,109~6,120~1,123~1,125~6,106~6,113 +n+d~113,3~~5,119~1,109~6,120~1,123~1,125~6,106~6,113 +n+d+d~114,3~~5,120~1,109~6,124~1,123~1,125~6,110~6,117 +n+d+d+r~115,3~~5,121~1,109~6,125~1,123~1,125~6,111~6,118 +n+d+y~116,3~~5,122~1,109~6,122~1,123~1,125~6,108~6,115 +n+d+r~117,3~~5,123~1,109~6,122~1,123~1,125~6,108~6,115 +n+d+h~118,3~~5,124~1,109~6,124~1,123~1,125~6,110~6,117 +n+d+h+r~119,3~~5,125~1,109~6,125~1,123~1,125~6,111~6,118 +n+d+h+y~120,3~~5,126~1,109~6,125~1,123~1,125~6,111~6,118 +n+n~121,3~~6,33~1,109~6,120~1,123~1,125~6,106~6,113 +n+n+y~123,3~~6,34~1,109~6,122~1,123~1,125~6,108~6,115 +n+p~124,3~~6,35~1,109~6,120~1,123~1,125~6,106~6,113 +n+p+r~125,3~~6,36~1,109~6,121~1,123~1,125~6,107~6,114 +n+ph~126,3~~6,37~1,109~6,120~1,123~1,125~6,106~6,113 +n+m~253,3~~6,39~1,109~6,120~1,123~1,125~6,106~6,113 +n+b+h+y~128,3~~6,38~1,109~6,125~1,123~1,125~6,111~6,118 +n+ts~129,3~~6,40~1,109~6,120~1,123~1,125~6,106~6,113 +n+y~130,3~~6,41~1,109~6,120~1,123~1,125~6,106~6,113 +n+r~131,3~~6,42~1,109~6,120~1,123~1,125~6,106~6,113 +n+w~132,3~~6,43~1,109~6,120~1,123~1,125~6,106~6,113 +n+w+y~133,3~~6,44~1,109~6,121~1,123~1,125~6,107~6,114 +n+s~134,3~~6,46~1,109~6,120~1,123~1,125~6,106~6,113 +n+s+y~135,3~~6,47~1,109~6,122~1,123~1,125~6,108~6,115 +n+h~136,3~~6,48~1,109~6,120~1,123~1,125~6,106~6,113 +n+h+r~137,3~~6,49~1,109~6,121~1,123~1,125~6,107~6,114 +p+t~138,3~~6,50~1,109~6,120~1,123~1,125~6,106~6,113 +p+t+y~139,3~~6,51~1,109~6,122~1,123~1,125~6,108~6,115 +p+t+r+y~140,3~~6,52~1,109~6,123~1,123~1,125~6,109~6,116 +p+d~190,4~~6,53~1,109~8,120~1,123~1,125~8,106~8,113 +p+n~141,3~~6,54~1,109~6,120~1,123~1,125~6,106~6,113 +p+n+y~249,3~~6,55~1,109~6,122~1,123~1,125~6,108~6,115 +p+p~143,3~~6,56~1,109~6,120~1,123~1,125~6,106~6,113 +p+m~144,3~~6,57~1,109~6,120~1,123~1,125~6,106~6,113 +p+l~145,3~~6,58~1,109~6,120~1,123~1,125~6,106~6,113 +p+w~146,3~~6,59~1,109~6,120~1,123~1,125~6,106~6,113 +p+s~147,3~~6,60~1,109~6,120~1,123~1,125~6,106~6,113 +p+s+n+y~148,3~~6,61~1,109~6,125~1,123~1,125~6,111~6,118 +p+s+w~149,3~~6,62~1,109~6,122~1,123~1,125~6,108~6,115 +p+s+y~150,3~~6,63~1,109~6,122~1,123~1,125~6,108~6,115 +b+g+h~151,3~~6,64~1,109~6,124~1,123~1,125~6,110~6,117 +b+dz~152,3~~6,65~1,109~6,120~1,123~1,125~6,106~6,113 +b+d~153,3~~6,66~1,109~6,120~1,123~1,125~6,106~6,113 +b+d+dz~154,3~~6,67~1,109~6,124~1,123~1,125~6,110~6,117 +b+d+h~155,3~~6,68~1,109~6,124~1,123~1,125~6,110~6,117 +b+d+h+w~156,3~~6,69~1,109~6,125~1,123~1,125~6,111~6,118 +b+t~157,3~~6,70~1,109~6,120~1,123~1,125~6,106~6,113 +b+n~250,3~~6,71~1,109~6,120~1,123~1,125~6,106~6,113 +b+b~159,3~~6,72~1,109~6,120~1,123~1,125~6,106~6,113 +b+b+h~254,3~~6,73~1,109~6,124~1,123~1,125~6,110~6,117 +b+b+h+y~161,3~~6,74~1,109~6,125~1,123~1,125~6,111~6,118 +b+m~162,3~~6,75~1,109~6,120~1,123~1,125~6,106~6,113 +b+h~163,3~~6,76~1,109~6,120~1,123~1,125~6,106~6,113~0F57 +b+h+N~164,3~~6,77~1,109~6,124~1,123~1,125~6,110~6,117 +b+h+n~165,3~~6,78~1,109~6,124~1,123~1,125~6,110~6,117 +b+h+m~251,3~~6,79~1,109~6,124~1,123~1,125~6,110~6,117 +b+h+y~167,3~~6,80~1,109~6,122~1,123~1,125~6,108~6,115 +b+h+r~168,3~~6,81~1,109~6,121~1,123~1,125~6,107~6,114 +b+h+w~169,3~~6,82~1,109~6,122~1,123~1,125~6,108~6,115 +m+ny~170,3~~6,83~1,109~6,121~1,123~1,125~6,107~6,114 +m+N~171,3~~6,84~1,109~6,120~1,123~1,125~6,106~6,113 +m+n~172,3~~6,85~1,109~6,120~1,123~1,125~6,106~6,113 +m+n+y~173,3~~6,86~1,109~6,122~1,123~1,125~6,108~6,115 +m+p~174,3~~6,87~1,109~6,120~1,123~1,125~6,106~6,113 +m+p+r~175,3~~6,88~1,109~6,122~1,123~1,125~6,108~6,115 +m+ph~176,3~~6,89~1,109~6,120~1,123~1,125~6,106~6,113 +m+b~177,3~~6,90~1,109~6,120~1,123~1,125~6,106~6,113 +m+b+h~178,3~~6,91~1,109~6,124~1,123~1,125~6,110~6,117 +m+b+h+y~179,3~~6,92~1,109~6,125~1,123~1,125~6,111~6,118 +m+m~180,3~~6,93~1,109~6,120~1,123~1,125~6,106~6,113 +m+l~181,3~~6,94~1,109~6,120~1,123~1,125~6,106~6,113 +m+w~182,3~~6,95~1,109~6,120~1,123~1,125~6,106~6,113 +m+s~183,3~~6,96~1,109~6,120~1,123~1,125~6,106~6,113 +m+h~184,3~~6,97~1,109~6,120~1,123~1,125~6,106~6,113 +y+y~185,3~~6,98~1,109~6,120~1,123~1,125~6,106~6,113 +y+r~186,3~~6,99~1,109~6,120~1,123~1,125~6,106~6,113 +y+w~187,3~~6,100~1,109~6,120~1,123~1,125~6,106~6,113 +y+s~188,3~~6,101~1,109~6,120~1,123~1,125~6,106~6,113 +r+kh~189,3~~6,102~1,109~6,120~1,123~1,125~6,106~6,113 +r+g+h~190,3~~6,103~1,109~6,121~1,123~1,125~6,107~6,114 +r+g+h+y~191,3~~6,104~1,109~6,123~1,123~1,125~6,109~6,116 +r+ts+y~192,3~~6,105~1,109~6,121~1,123~1,125~6,107~6,114 +r+tsh~193,3~~7,33~1,109~6,120~1,123~1,125~6,106~6,113 +r+dz+ny~194,3~~7,34~1,109~6,122~1,123~1,125~6,108~6,115 +r+dz+y~195,3~~7,35~1,109~6,121~1,123~1,125~6,107~6,114 +r+T~196,3~~7,36~1,109~6,120~1,123~1,125~6,106~6,113 +r+Th~197,3~~7,37~1,109~6,120~1,123~1,125~6,106~6,113 +r+D~198,3~~7,38~1,109~6,120~1,123~1,125~6,106~6,113 +r+N~199,3~~7,39~1,109~6,120~1,123~1,125~6,106~6,113 +r+t+w~33,4~~7,40~1,109~8,121~1,123~1,125~8,107~8,114 +r+t+t~34,4~~7,41~1,109~8,122~1,123~1,125~8,108~8,115 +r+t+s~35,4~~7,42~1,109~8,121~1,123~1,125~8,107~8,114 +r+t+s+n~36,4~~7,43~1,109~8,125~1,123~1,125~8,111~8,118 +r+t+s+n+y~37,4~~7,44~1,109~8,126~1,123~1,125~8,112~8,119 +r+th~38,4~~7,46~1,109~8,120~1,123~1,125~8,106~8,113 +r+th+y~39,4~~7,47~1,109~8,121~1,123~1,125~8,107~8,114 +r+d+d+h~40,4~~7,48~1,109~8,125~1,123~1,125~8,111~8,118 +r+d+d+h+y~41,4~~7,49~1,109~8,126~1,123~1,125~8,112~8,119 +r+d+y~42,4~~7,50~1,109~8,121~1,123~1,125~8,107~8,114 +r+d+h~43,4~~7,51~1,109~8,122~1,123~1,125~8,108~8,115 +r+d+h+m~44,4~~7,52~1,109~8,125~1,123~1,125~8,111~8,118 +r+d+h+y~252,4~~7,53~1,109~8,123~1,123~1,125~8,109~8,116 +r+d+h+r~46,4~~7,54~1,109~8,122~1,123~1,125~8,108~8,115 +r+p~47,4~~7,55~1,109~8,120~1,123~1,125~8,106~8,113 +r+b+p~48,4~~7,56~1,109~8,121~1,123~1,125~8,107~8,114 +r+b+b~49,4~~7,57~1,109~8,121~1,123~1,125~8,107~8,114 +r+b+h~50,4~~7,58~1,110~8,124~1,125~1,125~8,110~8,117 +r+m+m~51,4~~7,59~1,110~8,121~1,125~1,125~8,107~8,114 +r+y~52,4~~7,60~1,110~8,120~1,125~1,125~8,106~8,113 +r+w~196,4~~7,61~1,109~8,120~1,123~1,125~8,106~8,113 +r+sh~53,4~~7,62~1,109~8,120~1,123~1,125~8,106~8,113 +r+sh+y~54,4~~7,63~1,109~8,122~1,123~1,125~8,108~8,115 +r+Sh~55,4~~7,64~1,109~8,120~1,123~1,125~8,106~8,113 +r+Sh+N~56,4~~7,65~1,109~8,123~1,123~1,125~8,109~8,116 +r+Sh+N+y~57,4~~7,66~1,109~8,126~1,123~1,125~8,112~8,119 +r+Sh+m~58,4~~7,67~1,109~8,124~1,123~1,125~8,110~8,117 +r+Sh+y~59,4~~7,68~1,109~8,123~1,123~1,125~8,109~8,116 +r+s~60,4~~7,69~1,109~8,120~1,123~1,125~8,106~8,113 +r+h~61,4~~7,70~1,109~8,121~1,123~1,125~8,107~8,114 +r+k+Sh~62,4~~7,71~1,109~8,121~1,123~1,125~8,107~8,114 +l+g+w~63,4~~7,72~1,109~8,122~1,123~1,125~8,108~8,115 +l+b+y~64,4~~7,73~1,109~8,122~1,123~1,125~8,108~8,115 +l+m~65,4~~7,74~1,109~8,120~1,123~1,125~8,106~8,113 +l+y~66,4~~7,75~1,109~8,120~1,123~1,125~8,106~8,113 +l+w~67,4~~7,76~1,109~8,120~1,123~1,125~8,106~8,113 +l+l~68,4~~7,77~1,109~8,120~1,123~1,125~8,106~8,113 +l+h+w~197,4~~7,78~1,109~~1,123~1,125~8,106~8,113 +w+y~69,4~~7,79~1,109~8,121~1,123~1,125~8,107~8,114 +w+r~70,4~~7,80~1,109~8,121~1,123~1,125~8,107~8,114 +w+n~195,4~~7,81~1,109~8,120~1,123~1,125~8,106~8,113 +w+w~194,4~~7,82~1,109~8,120~1,123~1,125~8,106~8,113 +sh+ts~71,4~~7,83~1,109~8,120~1,123~1,125~8,106~8,113 +sh+ts+y~72,4~~7,84~1,109~8,122~1,123~1,125~8,108~8,115 +sh+tsh~73,4~~7,85~1,109~8,120~1,123~1,125~8,106~8,113 +sh+N~74,4~~7,86~1,109~8,120~1,123~1,125~8,106~8,113 +sh+n~75,4~~7,87~1,109~8,120~1,123~1,125~8,106~8,113 +sh+p~76,4~~7,88~1,109~8,120~1,123~1,125~8,106~8,113 +sh+b+y~77,4~~7,89~1,109~8,122~1,123~1,125~8,108~8,115 +sh+m~78,4~~7,90~1,109~8,120~1,123~1,125~8,106~8,113 +sh+y~79,4~~7,91~1,109~8,120~1,123~1,125~8,106~8,113 +sh+r+y~80,4~~7,92~1,109~8,121~1,123~1,125~8,107~8,114 +sh+l~81,4~~7,93~1,109~8,120~1,123~1,125~8,106~8,113 +sh+w+g~82,4~~7,94~1,109~8,122~1,123~1,125~8,108~8,115 +sh+w+y~83,4~~7,95~1,109~8,121~1,123~1,125~8,107~8,114 +sh+sh~84,4~~7,96~1,109~8,120~1,123~1,125~8,106~8,113 +Sh+k~85,4~~7,97~1,109~8,120~1,123~1,125~8,106~8,113 +Sh+k+r~86,4~~7,98~1,109~8,121~1,123~1,125~8,107~8,114 +Sh+T~87,4~~7,99~1,109~8,120~1,123~1,125~8,106~8,113 +Sh+T+y~88,4~~7,100~1,109~8,123~1,123~1,125~8,109~8,116 +Sh+T+r~89,4~~7,101~1,109~8,121~1,123~1,125~8,107~8,114 +Sh+T+r+y~90,4~~7,102~1,109~8,123~1,123~1,125~8,109~8,116 +Sh+T+w~91,4~~7,103~1,109~8,123~1,123~1,125~8,109~8,116 +Sh+Th~92,4~~7,104~1,109~8,120~1,123~1,125~8,106~8,113 +Sh+Th+y~93,4~~7,105~1,109~8,123~1,123~1,125~8,109~8,116 +Sh+N~94,4~~7,106~1,109~8,120~1,123~1,125~8,106~8,113 +Sh+N+y~95,4~~7,107~1,109~8,123~1,123~1,125~8,109~8,116 +Sh+D~96,4~~7,108~1,109~8,120~1,123~1,125~8,106~8,113 +Sh+Th~191,4~~7,109~1,109~8,120~1,123~1,125~8,106~8,113 +Sh+p~97,4~~7,110~1,109~8,120~1,123~1,125~8,106~8,113 +Sh+p+r~98,4~~7,111~1,109~8,121~1,123~1,125~8,107~8,114 +Sh+m~99,4~~7,112~1,109~8,120~1,123~1,125~8,106~8,113 +Sh+y~100,4~~7,113~1,109~8,121~1,123~1,125~8,107~8,114 +Sh+w~101,4~~7,114~1,109~8,120~1,123~1,125~8,106~8,113 +Sh+Sh~102,4~~7,115~1,109~8,120~1,123~1,125~8,106~8,113 +s+k+s~103,4~~7,116~1,109~8,124~1,123~1,125~8,110~8,117 +s+kh~104,4~~7,117~1,109~8,120~1,123~1,125~8,106~8,113 +s+ts+y~105,4~~7,118~1,109~8,122~1,123~1,125~8,108~8,115 +s+T~106,4~~7,119~1,109~8,120~1,123~1,125~8,106~8,113 +s+Th~107,4~~7,120~1,109~8,120~1,123~1,125~8,106~8,113 +s+T+y~108,4~~7,121~1,109~8,121~1,123~1,125~8,107~8,114 +s+t+r~109,4~~7,122~1,109~8,121~1,123~1,125~8,107~8,114 +s+t+w~110,4~~7,123~1,109~8,121~1,123~1,125~8,107~8,114 +s+th~111,4~~7,124~1,109~8,120~1,123~1,125~8,106~8,113 +s+th+y~112,4~~7,125~1,109~8,122~1,123~1,125~8,108~8,115 +s+n+y~113,4~~7,126~1,109~8,122~1,123~1,125~8,108~8,115 +s+n+w~114,4~~8,33~1,109~8,122~1,123~1,125~8,108~8,115 +s+ph~115,4~~8,34~1,109~8,120~1,123~1,125~8,106~8,113 +s+ph+y~116,4~~8,35~1,109~8,122~1,123~1,125~8,108~8,115 +s+y~117,4~~8,36~1,109~8,120~1,123~1,125~8,106~8,113 +s+r+w~118,4~~8,37~1,109~8,122~1,123~1,125~8,108~8,115 +s+s~119,4~~8,38~1,109~8,120~1,123~1,125~8,106~8,113 +s+s+w~120,4~~8,39~1,109~8,122~1,123~1,125~8,108~8,115 +s+h~121,4~~8,40~1,109~8,120~1,123~1,125~8,106~8,113 +s+w+y~122,4~~8,41~1,109~8,122~1,123~1,125~8,108~8,115 +h+ny~123,4~~8,42~1,109~8,120~1,123~1,125~8,106~8,113 +h+N~124,4~~8,43~1,109~8,120~1,123~1,125~8,106~8,113 +h+t~125,4~~8,44~1,109~8,120~1,123~1,125~8,106~8,113 +h+n~126,4~~8,46~1,109~8,120~1,123~1,125~8,106~8,113 +h+n+y~253,4~~8,47~1,109~8,122~1,123~1,125~8,108~8,115 +h+p~128,4~~8,48~1,109~8,120~1,123~1,125~8,106~8,113 +h+ph~129,4~~8,49~1,109~8,120~1,123~1,125~8,106~8,113 +h+m~130,4~~8,50~1,109~8,120~1,123~1,125~8,106~8,113 +h+y~131,4~~8,51~1,109~8,120~1,123~1,125~8,106~8,113 +h+l~132,4~~8,52~1,109~8,120~1,123~1,125~8,106~8,113 +h+s~133,4~~8,53~1,109~8,120~1,123~1,125~8,106~8,113 +h+s+w~134,4~~8,54~1,109~8,122~1,123~1,125~8,108~8,115 +h+w+y~135,4~~8,55~1,109~8,121~1,123~1,125~8,107~8,114 +k+Sh+N~136,4~~8,56~1,109~8,124~1,123~1,125~8,110~8,117 +k+Sh+m~137,4~~8,57~1,109~8,124~1,123~1,125~8,110~8,117 +k+Sh+m+y~138,4~~8,58~1,109~8,126~1,123~1,125~8,112~8,119 +k+Sh+y~139,4~~8,59~1,109~8,123~1,123~1,125~8,109~8,116 +k+Sh+r~140,4~~8,60~1,109~8,123~1,123~1,125~8,109~8,116 +k+Sh+l~141,4~~8,61~1,109~8,124~1,123~1,125~8,110~8,117 +k+Sh+w~249,4~~8,62~1,109~8,122~1,123~1,125~8,108~8,115 +a+y~143,4~~8,63~1,109~8,120~1,123~1,125~8,106~8,113 +a+r~144,4~~8,64~1,109~8,120~1,123~1,125~8,106~8,113 +a+r+y~145,4~~8,65~1,109~8,121~1,123~1,125~8,107~8,114 + +//numbers +0~190,1~~10,48~~~~~~~0F20 +1~191,1~~10,49~~~~~~~0F21 +2~192,1~~10,50~~~~~~~0F22 +3~193,1~~10,51~~~~~~~0F23 +4~194,1~~10,52~~~~~~~0F24 +5~195,1~~10,53~~~~~~~0F25 +6~196,1~~10,54~~~~~~~0F26 +7~197,1~~10,55~~~~~~~0F27 +8~198,1~~10,56~~~~~~~0F28 +9~199,1~~10,57~~~~~~~0F29 +>0~50,5~~9,52 +>1~51,5~~9,53 +>2~52,5~~9,54 +>3~53,5~~9,55 +>4~54,5~~9,56 +>5~55,5~~9,57 +>6~56,5~~9,58 +>7~57,5~~9,59 +>8~58,5~~9,60 +>9~59,5~~9,61 +<0~60,5~~9,62 +<1~61,5~~9,63 +<2~62,5~~9,64 +<3~63,5~~9,65 +<4~64,5~~9,66 +<5~65,5~~9,67 +<6~66,5~~9,68 +<7~67,5~~9,69 +<8~68,5~~9,70 +<9~69,5~~9,71 +1/2~70,5~~9,72~~~~~~~0F33 +1+1/2~71,5~~9,73~~~~~~~0F2A +2+1/2~72,5~~9,74~~~~~~~0F2B +3+1/2~73,5~~9,75~~~~~~~0F2C +4+1/2~74,5~~9,76~~~~~~~0F2D +5+1/2~75,5~~9,77~~~~~~~0F2E +6+1/2~76,5~~9,78~~~~~~~0F2F +7+1/2~77,5~~9,79~~~~~~~0F30 +8+1/2~78,5~~9,80~~~~~~~0F31 +9+1/2~79,5~~9,81~~~~~~~0F32 + + +//punctuation +_~32,1~~1,32 + ~45,1~~1,45~~~~~~~0F0B +_~32,1~~2,32 + ~45,1~~2,45~~~~~~~0F0B +_~32,1~~3,32 + ~45,1~~3,45~~~~~~~0F0B +_~32,1~~4,32 + ~45,1~~4,45~~~~~~~0F0B +_~32,1~~5,32 + ~45,1~~5,45~~~~~~~0F0B +_~32,1~~6,32 + ~45,1~~6,45~~~~~~~0F0B +_~32,1~~7,32 + ~45,1~~7,45~~~~~~~0F0B +_~32,1~~8,32 + ~45,1~~8,45~~~~~~~0F0B +_~32,1~~9,32 + ~45,1~~9,45~~~~~~~0F0B +_~32,1~~10,32 + ~45,1~~10,45~~~~~~~0F0B + +//bindus +`~241,1~~8,94~~~~~~~0F83 +iM~243,1~~8,96 +iM~244,1~~8,97 +-iM~245,1~~8,98 +eM~246,1~~8,99 +eM~247,1~~8,100 +aiM~248,1~~8,101 +oM~249,1~~8,102 +oM~250,1~~8,103 +auM~251,1~~8,104 + +//reduced-height consonants +k~180,1~~1,92~1,109~1,111~1,123~1,125~10,118~10,120 +g~181,1~~1,93~1,109~1,111~1,123~1,125~10,118~10,120 +ny~182,1~~1,94~1,109~1,112~1,123~1,125~10,115~10,121 +t~183,1~~1,95~1,109~1,112~1,123~1,125~10,115~10,121 +d~184,1~~1,96~1,109~1,111~1,123~1,125~10,118~10,120 +n~185,1~~1,97~1,109~1,111~1,123~1,125~10,118~10,120 +zh~186,1~~1,98~1,109~1,111~1,123~1,125~10,118~10,120 +sh~187,1~~1,99~1,109~1,111~1,123~1,125~10,118~10,120 +h~188,1~~1,100~1,109~1,112~1,123~1,125~10,119 +rt~189,1~~1,101~1,109~1,113~1,123~1,125~10,116~10,124 +h~156,1~~1,102~1,109~~1,123~1,125~10,114~10,122 + +//half-height consonants +k~200,5~~10,71~1,109~~1,123~1,125~~~0F90 +kh~201,5~~10,72~1,109~~1,123~1,125~~~0F91 +g~202,5~~10,73~1,109~~1,123~1,125~~~0F92 +g+h~203,5~~10,74~1,109~~1,123~1,125~~~0F93 +ng~204,5~~10,75~1,109~~1,123~1,125~~~0F94 +c~205,5~~10,76~1,109~~1,123~1,125~~~0F95 +ch~206,5~~10,77~1,109~~1,123~1,125~~~0F96 +j~207,5~~10,78~1,109~~1,123~1,125~~~0F97 +ny~208,5~~10,79~1,109~~1,123~1,125~~~0F99 +T~209,5~~10,80~1,109~~1,123~1,125~~~0F9A +Th~210,5~~10,81~1,109~~1,123~1,125~~~0F9B +D~211,5~~10,82~1,109~~1,123~1,125~~~0F9C +D+h~212,5~~10,83~1,109~~1,123~1,125~~~0F9D +N~213,5~~10,84~1,109~~1,123~1,125~~~0F9E +t~214,5~~10,85~1,109~~1,123~1,125~~~0F9F +th~215,5~~10,86~1,109~~1,123~1,125~~~0FA0 +d~216,5~~10,87~1,109~~1,123~1,125~~~0FA1 +d+h~217,5~~10,88~1,109~~1,123~1,125~~~0FA2 +n~218,5~~10,89~1,109~~1,123~1,125~~~0FA3 +p~219,5~~10,90~1,109~~1,123~1,125~~~0FA4 +ph~220,5~~10,91~1,109~~1,123~1,125~~~0FA5 +b~221,5~~10,92~1,109~~1,123~1,125~~~0FA6 +b+h~222,5~~10,93~1,109~~1,123~1,125~~~0FA7 +m~223,5~~10,94~1,109~~1,123~1,125~~~0FA8 +ts~224,5~~10,95~1,110~~1,125~1,126~~~0FA9 +tsh~225,5~~10,96~1,110~~1,125~1,126~~~0FAA +dz~226,5~~10,97~1,110~~1,125~1,126~~~0FAB +dz+h~227,5~~10,98~1,110~~1,125~1,126~~~0FAC +w~228,5~~10,99~1,109~~1,123~1,125~~~0FBA +zh~229,5~~10,100~1,109~~1,123~1,125~~~0FAE +z~230,5~~10,101~1,109~~1,123~1,125~~~0FAF +'~231,5~~10,102~1,109~~1,123~1,125~~~0FB0 +y~232,5~~10,103~1,109~~1,123~1,125~~~0FBB +r~233,5~~10,104~1,109~~1,123~1,125~~~0FBC +l~234,5~~10,105~1,109~~1,123~1,125~~~0FB3 +sh~235,5~~10,106~1,109~~1,123~1,125~~~0FB4 +Sh~236,5~~10,107~1,109~~1,123~1,125~~~0FB5 +s~237,5~~10,108~1,109~~1,123~1,125~~~0FB6 +h~238,5~~10,109~1,109~~1,123~1,125~~~0FB6 +a~239,5~~10,110~1,109~~1,123~1,125~~~0FB8 +k+Sh~240,5~~10,111~1,109~~1,123~1,125~~~0FB9 + +//vowels +i~220,1~~1,109~~~~~~~~~8,96 +i~221,1~~1,110~~~~~~~~~8,97 +u~165,1~~1,111 +u~226,1~~1,112 +u~167,1~~1,113 +u~168,1~~1,114 +u~169,1~~1,115 +u~176,1~~1,116 +u~177,1~~1,117 +u~223,1~~1,118 +u~224,1~~1,119 +u~225,1~~1,120 +u~227,1~~1,121 +u~228,1~~1,122 +e~232,1~~1,123~~~~~~~~~8,99 +e~233,1~~1,124~~~~~~~~~8,100 +o~235,1~~1,125~~~~~~~~~8,102 +o~236,1~~1,126~~~~~~~~~8,103 +u~178,1~~2,111 +u~179,1~~2,112 +u~168,1~~2,113 +u~169,1~~2,114 +u~225,1~~2,115 +u~228,1~~2,116 +u~229,1~~2,117 +u~230,1~~2,118 +u~231,1~~2,119 +A~201,2~~4,106 +A~202,2~~4,107 +A~203,2~~4,108 +A~204,2~~4,109 +A~205,2~~4,110 +A~206,2~~4,111 +A~207,2~~4,112 +U~211,2~~4,113 +U~212,2~~4,114 +U~213,2~~4,115 +U~214,2~~4,116 +U~215,2~~4,117 +U~216,2~~4,118 +U~217,2~~4,119 +u~224,2~~4,120 +u~225,2~~4,121 +u~226,2~~4,122 +u~227,2~~4,123 +u~228,2~~4,124 +u~229,2~~4,125 +u~230,2~~4,126 +A~201,3~~6,106 +A~202,3~~6,107 +A~203,3~~6,108 +A~204,3~~6,109 +A~205,3~~6,110 +A~206,3~~6,111 +A~207,3~~6,112 +U~211,3~~6,113 +U~212,3~~6,114 +U~213,3~~6,115 +U~214,3~~6,116 +U~215,3~~6,117 +U~216,3~~6,118 +U~217,3~~6,119 +u~224,3~~6,120 +u~225,3~~6,121 +u~226,3~~6,122 +u~227,3~~6,123 +u~228,3~~6,124 +u~229,3~~6,125 +u~230,3~~6,126 +A~201,4~~8,106 +A~202,4~~8,107 +A~203,4~~8,108 +A~204,4~~8,109 +A~205,4~~8,110 +A~206,4~~8,111 +A~207,4~~8,112 +U~211,4~~8,113 +U~212,4~~8,114 +U~213,4~~8,115 +U~214,4~~8,116 +U~215,4~~8,117 +U~216,4~~8,118 +U~217,4~~8,119 +u~224,4~~8,120 +u~225,4~~8,121 +u~226,4~~8,122 +u~227,4~~8,123 +u~228,4~~8,124 +u~229,4~~8,125 +u~230,4~~8,126 +A~161,1~~10,114 +A~162,1~~10,115 +A~163,1~~10,116 +A~164,1~~10,117 +A~211,1~~10,118 +A~212,1~~10,119 +U~213,1~~10,120 +U~214,1~~10,121 +U~215,1~~10,122 +U~216,1~~10,123 +U~217,1~~10,124 +U~218,1~~10,125 +U~219,1~~10,126 + + +cantillation sign,heavy beat~80,5~~9,82~~~~~~~0FC0 +cantillation sign,light beat~81,5~~9,83~~~~~~~0FC1 +cantillation sign,cang.te-u~82,5~~9,84~~~~~~~0FC2 +cantillation sign sbub.chal~83,5~~9,85~~~~~~~0FC3 +zhi.rol.btags~84,5~~9,86 + +sher.bu~90,5~~9,88 +nyi.zla~91,5~~9,89 +kuruka~92,5~~9,90 +no name~93,5~~9,91 + +dzud.rtags.me.long.can~94,5~~9,92~~~~~~~0F13 +dbu.khang.g-yon~208,1~~9,93~~~~~~~0F3C +dbu.khang.g-yas~209,1~~9,94~~~~~~~0F3D +gug.rtags.gyon~95,5~~9,95~~~~~~~0F3A +gug.rtags.gyas~96,5~~9,96~~~~~~~0F3B +yungs.drung (reversed)~97,5~~9,97 +yungs.drung (standard)~98,5~~9,98 + +mchan rtags trailing~99,5~~9,99 +mchan rtags leading~100,5~~9,100 + +mtshan.rtags~101,5~~9,101~~~~~~~0F37 +mtshan.rtags zhes.sa~102,5~~9,102~~~~~~~0F37 +che.mgo~103,5~~9,103~~~~~~~0F35 +kuruka~104,5~~9,104~~~~~~~0FBE~ +Kuruka.mig.lda~105,5~~9,105~~~~~~~0FBF +ornament~106,5~~9,106~~~~~~~0F36 +yang.rtags~107,5~~9,107~~~~~~~0F87 +lci.rtags~108,5~~9,108~~~~~~~0F86 +mchu.can~109,5~~9,109~~~~~~~0F89~ +gru.can.rgyings~110,5~~9,110~~~~~~~0F8A~ +gru.med.gyings~111,5~~9,111~~~~~~~0F8B + +single vhite pebble~115,5~~9,115~~~~~~~0F1A +single black pebble~116,5~~9,116~~~~~~~0F1D +double vhite pebble~117,5~~9,117~~~~~~~0F1B +double black pebble~118,5~~9,118~~~~~~~0F1E +vhite and black pebble~119,5~~9,119~~~~~~~0F1F +triple vhite pebble~120,5~~9,120~~~~~~~0F1C +triple black pebble~121,5~~9,121~~~~~~~0FCF + +122,5~~9,122 +123,5~~9,123 +124,5~~9,124 +125,5~~9,125 +126,5~~9,126 +128,5~~10, 33 + +logo sign chad.rtags~129,5~~10,34~~~~~~~0F15 +logo sign lhag.rtags~130,5~~10,35~~~~~~~0F16 +sgra.gcan.char.rtags~131,5~~10,36~~~~~~~0F17 +khyud.pa~132,5~~10,37~~~~~~~0F18 +sdong.tshugs~133,5~~10,38~~~~~~~0F19 +yar.tshes.rtags~134,5~~10,39~~~~~~~0F3E +mar.tshes.rtags~135,5~~10,40~~~~~~~0F3F + +rinchen shad~203,1~~1,103~~~~~~~0F11 +sbrul shad~204,1~~1,104~~~~~~~0F08 +gter tsheg~206,1~~1,105~~~~~~~0F14 +abbreviation sign~207,1~~1,106~~~~~~~0F34 + +utsama ka~57,2~~3,59~1,109~4,120~1,125~1,123~4,106~4,113 +utsama kha~58,2~~3,60~1,109~4,120~1,125~1,123~4,106~4,113 + +ra.mgo~173,4~~8,66 +tza.'phru~174,4~~8,67~~~~~~~0F39 +reversed tza.'phru~145,5~~8,68 +wa.btags~159,1~~8,69~~~~~~~0FAD~ +ya.btags~175,4~~8,70~~~~~~~0FB1 +ra.btags ~176,4~~8,71~~~~~~~0FB2 +damaru.rtags~178,4~~8,72~~~~~~~0F88 +half a.chen~179,4~~8,73~~~~~~~0F01 +ITHI secret sign~180,4~~8,74 +Terton's mark~181,4~~8,75 +Terton's mark~182,4~~8,76 +Terton's mark~183,4~~8,77 +Terton's mark~149,5~~8,78 +Terma mark~184,4~~8,79 +Terma mark~185,4~~8,80 +Terma mark~186,4~~8,81 +Mark~187,4~~8,82 + +Chinese letter~155,5~~10,60 +Special combination~156,5~~10,61 + +dril.bu~190,5~~10,62~~~~~~~0FC4 +rdo.rje~191,5~~10,63~~~~~~~0FC5 +padma.gdan~192,5~~10,64~~~~~~~0FC6 +rdo.rje.rgya.gram~193,5~~10,65~~~~~~~0FC7 +phur.ba~194,5~~10,66~~~~~~~0FC8 +nor.bu~195,5~~10,67~~~~~~~0FC9 +nor.bu.gnyis.khyil~196,5~~10,68~~~~~~~0FCA +nor.bu.gsum.khyil~197,5~~10,69~~~~~~~0FCB +nor.bu.bzhi.khyil~198,5~~10,70~~~~~~~0FCC + +large anushvara~238,1~~8,90~~~~~~~0F7E + +bindu + datse~241,1~~8,94~~~~~~~0F83 +bindu + datse + thigle~242,1~~8,95~~~~~~~0F82 +bindu + kigu~243,1~~8,96 +bindu + short gigu~244,1~~8,97 +bindu + log yig gigu~245,1~~8,98 +bindu + normal drengu~246,1~~8,99 +bindu + short drengbu~247,1~~8,100 +bindu + double drengbu~248,1~~8,101 +bindu + normal naro~249,1~~8,102 +bindu + raised naro~250,1~~8,103 +bindu + double naro~251,1~~8,104 +virama~252,1~~8,105~~~~~~~0F84 + +zhu.yig.mgo.rgyan~33,5~~9,33~~~~~~~0F0A +bka'.shog.mgo.rgyan~34,5~~9,34 +mnyam.yig.mgo.rgyan~35,5~~9,35 +mnyam.yig.mgo.rgyan~36,5~~9,36~~~~~~~0F09 +37,5~~9,37 +zla tse gcig~210,1~~9,38~~~~~~~0F04 +half zla tse gcig~200,1~~9,39~~~~~~~0F05 +zla tse gnyis~201,1~~9,40 +yig.mgo.phur.shad~38,5~~9,41~~~~~~~0F06 +Yig.mgo.tsheg.shad~39,5~~9,42~~~~~~~0F07 + +shad + single tsheg~40,5~~9,43~~~~~~~0F0F +shad (hooked) + single tsheg~41,5~~9,44 +shad + double tsheg~42,5~~9,46 +shad + single ornament~43,5~~9,47~~~~~~~0F10 +sbrul.shad + single ornament~44,5~~9,48 +sbrul.shad + double ornament~46,5~~9,49 +sbrul.shad variant form~47,5~~9,50 +rgya.gram.shad~48,5~~9,51~~~~~~~0F12 + +hard tsheg~205,1~~1,108~~~~~~~0F0C + +reversed hu~147,5~~10,43 +Inverted ha~148,5~~10,44 \ No newline at end of file